Say I have the following class structure:
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);
}
}
Here's the result I would expect from running the following code:
Test.SetAndPrintName(new A(), "a");
Test.SetAndPrintName(new B(), "b");
----
a
b set in B
Instead, I get:
a
b
Since that didn't work, I tried casting value
in SetAndPrintName
, which is ugly but worked as expected:
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
I also tried making the Name
property virtual/overridden and that worked too:
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
The question is: why doesn't new
work like the others? The generic method knows that value
is of type B
, so why does C# treat it as an A
in one case out of three?
The generic method knows that value is of type B
Not at compile-time it doesn't. When the compiler sees value.Name
, it has to resolve that to one member... and the only member available to it is the one declared in A
.
Think of your two properties as being entirely separate - as if they had different names. Let's call them NameA
and NameB
.
Your code is effectively:
public static void SetAndPrintName<T>(T value, string name) where T : A {
value.NameA = name;
Console.WriteLine(value.NameA);
}
That's valid, because the compiler can resolve NameA
against A
, and T
is constrained to be an A
.
If you try to explicitly use NameB
, however:
// Invalid
public static void SetAndPrintName<T>(T value, string name) where T : A {
value.NameB = name;
Console.WriteLine(value.NameB);
}
... that won't work, because the compiler can't resolve NameB
for T
.
When you use a virtual property, then you've got a single declared member which has its implementation overridden in B
... which is precisely what you normally do when you want to modify behaviour.
If you want to use different declared members based on execution-time type (without knowing it beforehand) you could use dynamic
:
public static void SetAndPrintName(dynamic value, string name) {
value.Name = name;
Console.WriteLine(value.Name);
}
See more on this question at Stackoverflow