In our project we added some code and got strange results so I tried to isolate the problem and found out that hiding a property by using new in stead of override Unity is not working like expected. (I tested it in Unity 2017.1)
To make it more clear I wrote a small script to demonstrate:
public interface ITestData
{
int Value1 { get; set; }
string Value2 { get; set; }
}
public class TestData : ITestData
{
public int Value1 { get; set; }
public string Value2 { get; set; }
}
public class TestDataOverride : TestData
{
private int _value1;
public new int Value1
{
get { return _value1; }
set { _value1 = value * value; }
}
}
public class Tester : MonoBehaviour
{
protected void Start()
{
Debug.Log(string.Format("========START {0}========", DateTime.Now));
var dummyData = new List<ITestData>
{
new TestDataOverride { Value1 = 3, Value2 = "Expecting 9 but getting 0" },
new TestDataOverride { Value1 = 4, Value2 = "Expecting 16 but getting 0" }
};
for (var i = 0; i < dummyData.Count; i++)
{
var item = dummyData[i];
Debug.Log("Result: " + item.Value1 + " - " + item.Value2 + " ");
}
Debug.Log("======= DONE =======");
}
}
When I set a breakpoint in Visual Studio (2013 pro) and read the values in the for loop item.Value1 shows the expected value but in the console it shows 0.
You’re accessing the dummyData values through the ITestData interface. TestDataOverride implements ITestData through TestData, so you’re getting TestData’s Value1 and Value2 here.
To have TestDataOverride’s Value1 override, you have to make the property override. Be very careful with the New keyword - it makes the same object return different values depending on which class you’re looking at it as:
public class C1 {
public string value { get; set; }
}
public class C2 : C1 {
public new string value { get; set; }
}
void Test() {
C2 c2 = new C2();
C1 c1 = (C1) c2;
c2.value = "c2's value";
c1.value = "c1's value";
Debug.Log(c2.value); //prints "c2's value"
}
To make that thing print “c1’s value” (in your case 9 or 16 instead of 0), make C1.value be virtual and C2.value be override.
EDIT: oh, by the way:
the debugger shows you all of the members of the type, in this case TestDataOverride’s types. To see the 0 in the debugger, fold out the “base” field and look at the Value1 there.
It works! There’s no bug! I don’t think you’re understanding what the new keyword does in this instance. The output you’re getting is the correct one, and the one you’d see if you create a standard C# application. Replace Test in my example with Main and Debug.Log with Console.WriteLine and put the code in a console app. It’ll still print “c2’s value”
Using the new keyword here causes TestDataOverride.Value1 to be a different property than ITestData.Value1. Your TestDataOverride objects have two fields named Value1. That’s insane and confusing and you should never do it, but that’s what you’ve done and the program is doing the correct thing.
When your for-loop is run, the runtime tries to get the thing that matches ITestData.Value1. That’s not TestDataOverride’s own .Value1, it’s the Value1 that you’ve inherited from TestData, so that’s used.
This is a good example of how new and override are not the same thing.
When you have a base class reference, the reference only knows about the type it’s currently referencing.
An ITestData reference only knows about the class it’s been implemented in (in your case, TestData). How is it supposed to know about a new Value1 object in TestDataOverride?
Or put more simply: If you have a Value3 member in TestDataOverride, you wouldn’t expect to be able to access that though ITestData, right? You’d have to cast it to TestDataOverride before accessing Value3.
With virtual, the base reference at least knows to look for overrides. With “new”, there’s no way it can know to search for override (and honestly you wouldn’t want it to, unless you wanted EVERYTHING to be searched for possible overrides anywhere, in which case doing, well, just about anything would be slow).
I’ve also tried it as a console application. The only difference is that, when debugging and placing a break point, the value I get from the debugger is 0 for value1 as it should be, but when I debug it as a Unity project is shows the wrong value (9 and 16) but prints the right value’s for value1 (0, and 0).
Sounds like a bug in the debugger. It’s pretty easy to get confused when using the new operator, and therefore why I actively avoid using it. Any possible benefits you could gain from it is easily nullified by the pitfalls and potential confusion it can create.
You could write code today, and remember to use “base.Value1” if you wanted to reference the original version, but then 6 months from now, it’s easy to forget about that and add bugs when you try to modify that code.