【问题标题】:Generics preventing virtual function call?防止虚函数调用的泛型?
【发布时间】:2020-08-17 14:15:48
【问题描述】:

多年后我正在重温我的 C#。我很生疏,所以这可能是我而不是编译器。

虚拟函数调用似乎停止工作,正如我对泛型所期望的那样。代码如下:

namespace ConsoleApp1 {

    public abstract class Root {
        // force me to implenent ToString() in subclasses
        public abstract new string ToString();
    }

    public class Mammal : Root {
        string m;
        public Mammal(string mIn) { m = mIn; }
        public override string ToString() => m;
    }

    // allow me to give a mammal a name:
    public class NamedMammal : Root {
        Mammal m; string name;
        public NamedMammal(Mammal mIn, string nameIn) {
            m = mIn; name = nameIn;
        }
        public override string ToString() =>
            $"({m.ToString()} has name {name})";
    }

    // but I may want to name many things, so make 
    //the thing being named generic:
    public class NamedThing<Tthing> : Root {
        Tthing thing; string name;
        public NamedThing(Tthing thingIn, string nameIn) {
            thing = thingIn; name = nameIn;
        }
        public override string ToString() =>
            $"({thing.ToString()} has name {name})";
    }
    // I can name tractors or planets if I want to now

    class Program {
        static void Main() {

            Mammal c = new Mammal("cow");
            NamedMammal nm = new NamedMammal(c, "daisy");
            Console.WriteLine(nm.ToString());
            // output, as expected:
            // (cow has name daisy)
            
            Mammal f = new Mammal("fox");
            NamedThing<Mammal> nt = 
                new NamedThing<Mammal>(f, "freddie");
            Console.WriteLine(nt.ToString());
            // not what I expected:
            // (ConsoleApp1.Mammal has name freddie)
        }
    }
}

它显然没有丢失类型,因为它将它输出为“ConsoleApp1.Mammal”,所以它知道它是一种哺乳动物,但它似乎没有像我预期的那样在它上面调用 ToString()。我猜它正在调用最基础的对象 ToString() 而不是最派生的。

这是按设计的吗,如果可以的话,有人可以给我一些指示,也许是如何处理这个问题的想法,因为我不想手写一堆样板文件。

【问题讨论】:

  • 它调用object.ToString(),因为你没有将Tthing限制为Root,所以它默认使用object的方法。编译器不知道你的Tthing 有一个新的ToString() 方法。
  • 因为你使用的是new,所以调用ToString的默认实现是行不通的。如果你想强制你的派生类覆盖ToString,你应该使用public override abstract string ToString();
  • @CodeCaster:AFAIK 编译器确实知道,这是因为根目录中 ToString() 中的“新”。注释掉'public class NamedThing中的ToString(),你会得到编译器错误“'NamedThing'没有实现继承的抽象成员'Root.ToString()'”。
  • @DavidG:成功了!谢谢
  • 谨慎使用会员隐藏功能。

标签: c# generics visual-studio-2019 vtable c#-8.0


【解决方案1】:

问题在于方法object.ToString()Root.ToString() 没有任何关系。这是您的设计。

NamedMammal 类中,您期望实现RootMammal 类型的事物。所以编译器知道方法Root.ToString()保证存在于给定的实例上。然后在NamedMammal.ToString() 方法中,您调用MammalRoot.ToString() 实现,因为它隐藏(而不是覆盖)object.ToString()

但是对于NamedThing 类,您的事物 不需要实现Root.ToString(),因此在方法NamedThing.ToString() 中调用thing.ToString() 将调用object.ToString()。这不会导致调用Root.ToString() 方法,因为它不是object.ToString() 的压倒一切的虚拟方法。

对此的解决方案是将NamedThing 的类型参数设置为实现Root,例如:

public class NamedThing<Tthing> : Root where Tthing : Root 
{ .. }

另一种解决方案是替换 new in

public abstract new string ToString();

override

public abstract override string ToString();

这样实现类型会覆盖 object.ToString() 而不是不相关的类似命名方法。


但请注意,您至少需要第一个解决方案来强制与 NamedThing 一起使用的类型实现 ToString()

【讨论】:

  • 也许提到避免new 以停止阴影的可能性?
  • Ackdari:我已经接受了您的回答,因为我可以看到子类型(Root where Tthing : Root)是有道理的,我明白没有它为什么它会转而使用 Object。我将通过其余部分并尝试理解。就像我说的,我很生疏,C# 已经成长了很多。还要感谢@DavidG
猜你喜欢
  • 1970-01-01
  • 2018-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-03
  • 1970-01-01
  • 2017-02-15
  • 1970-01-01
  • 2017-03-12
相关资源
最近更新 更多