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

本文关键字:隐藏 继承 成员 泛型 修改 派生 | 更新日期: 2023-09-27 18:11:10

假设我有如下的类结构:

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不像其他的一样工作?泛型方法知道valueB类型,那么为什么c#在三种情况中有一种情况将其视为A呢?

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

泛型方法知道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);
}

…这不起作用,因为编译器无法将NameB解析为T

当你使用虚拟属性时,你就有了一个声明的成员,它的实现B中被覆盖…当你想要改变行为时,这正是你通常所做的。

如果你想根据执行时类型使用不同的声明成员(事先不知道),你可以使用dynamic:

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

如果你不想使用动态关键字,你可以引入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);
    }
}