C#泛型:修改派生类中隐藏的继承成员

3

假设我有以下的类结构:

public class A {
    public string Name { get; set; }
}

public class B : A {
    public new string Name {
        get { return base.Name; }
        set { base.Name = value + " set in B"; }
    }
}

public static class Test {
    public static void SetAndPrintName<T>(T value, string name) where T : A {
        value.Name = name;

        Console.WriteLine(value.Name);
    }
}

以下是运行以下代码后我期望得到的结果:
Test.SetAndPrintName(new A(), "a");
Test.SetAndPrintName(new B(), "b");
----
a
b set in B

相反,我得到了:
a
b

由于那种方法不起作用,我尝试在SetAndPrintName中转换value,这样做有些丑陋,但效果符合预期:

public static void SetAndPrintName<T>(T value, string name) where T : A {
    if (value is B) {
        ((B)(object)value).Name = name;
    } else {
        value.Name = name;
    }

    Console.WriteLine(value.Name);
}
----
Test.SetAndPrintName(new A(), "a");
Test.SetAndPrintName(new B(), "b");
----
a
b set in B

我也尝试将 Name 属性设为虚拟/重写,这也可行:
public class A {
    public virtual string Name { get; set; }
}

public class B : A {
    public override string Name {
        get { return base.Name; }
        set { base.Name = value + " set in B"; }
    }
}
----
Test.SetAndPrintName(new A(), "a");
Test.SetAndPrintName(new B(), "b");
----
a
b set in B

问题是:为什么new不像其他方法一样工作?通用方法知道value的类型是B,那么为什么C#在三种情况中有一种情况将其视为A

new 关键字并不固有地意味着 virtual。泛型实现也无法改变这一点。 - haim770
2个回答

6

这个通用方法知道value是B类型的。

但在编译时并不知道。当编译器看到value.Name时,它必须将其解析为一个成员......并且唯一可用于它的成员是在A中声明的成员。

将两个属性视为完全分开——就好像它们有不同的名称。我们将它们称为NameANameB

你的代码实际上相当于:

public static void SetAndPrintName<T>(T value, string name) where T : A {
    value.NameA = name;
    Console.WriteLine(value.NameA);
}

这是正确的,因为编译器可以通过A解析NameA,并且T被限制为A

但是,如果您试图显式使用NameB

// Invalid
public static void SetAndPrintName<T>(T value, string name) where T : A {
    value.NameB = name;
    Console.WriteLine(value.NameB);
}

这种方法行不通,因为编译器无法解析T中的NameB

当您使用一个虚拟属性时,您有一个单一声明成员,在B中覆盖了它的实现... 这正是您通常想要修改行为时所做的。

如果您想基于执行时类型(事先不知道)使用不同的声明成员,则可以使用dynamic

public static void SetAndPrintName(dynamic value, string name) {
    value.Name = name;
    Console.WriteLine(value.Name);
}

5
我可以停止写我的回答了 :P - khellang
啊,好的,这很有道理。我没有想到new会创建一个cough新属性而不是覆盖现有的实现。感谢你的解释! - Brian Kintz

0

如果您不想使用动态关键字,可以引入IA接口,让两种类型都继承它,并将IA用作泛型方法的类型约束。这样就能产生您期望的输出。

    public interface IA
{
    string Name { get; set; }
}

public class A : IA
{
    public string Name { get; set; }
}

public class B : A, IA
{
    public new string Name
    {
        get { return base.Name; }
        set { base.Name = value + " set in B"; }
    }
}

public static class Test
{
    public static void SetAndPrintName<T>(T value, string name) where T : IA
    {
        value.Name = name;

        Console.WriteLine(value.Name);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接