I ran into a problem when using a DRange1d as a property in one of my classes where it would never change from null.
I managed to recreate this problem with a keyin. (The property is static just for this example to run with the keyin)
private static DRange1d testRange { get; set; } public static void RunTestCode(String unparsed) { testRange = DRange1d.NullRange; testRange.Extend(73.2); Debug.Print("testRange: " + (testRange.IsExactNull ? "Null": "Not Null")); DRange1d testRange2 = DRange1d.NullRange; testRange2.Extend(73.2); Debug.Print("testRange2: " + (testRange2.IsExactNull ? "Null": "Not Null")); }
The output is:
testRange: NulltestRange2: Not Null
Can someone explain to me what is going on?
Thanks,
Maury
Hi Maury,
please, don't ask such questions, they create too perfect chance to procrastinate under "it's important and interesting question" mimics :-))))
Maury said:I ran into a problem when using a DRange1d as a property
Yes, based on my testing (seriously, too much time spent with this procrastination ;-) , the property usage is on from important sources of the discussed behaviour.
Maury said:Can someone explain to me what is going on?
Yes, at least a bit ;-)
It seems the problem is part of a chain of several different conditions and rules how value types are treated and how static properties (accessors) are initialized and in what order. But honestly, it's still just my guess, I am running out of my current knowledge about C# rules and internals.
General C# rules:
When applied to your code and how DRange1d struct is defined:
Solution:
I hope it will help you, but please be aware it's just a guess based on hands on testing, not analysis based on knowledge of C# rules and internals ;-)
With regards,
Jan
Bentley Accredited Developer: iTwin Platform - AssociateLabyrinth Technology | dev.notes() | cad.point
From the looks of it, it probably does have something to do with the struct type being copied and how properties are handled.
Jan Šlegr said:Solution: Do not use auto property, but declare private variable explicitly (and make it available through normal property).
For this solution, the only way to get it to work properly was to run "Extend" on the private variable directly and not the property exposing it.
When calling extend on the property, it seems like the extend method is being called on the copy and not reflecting on the private variable.
internal class TestClass { private DRange1d _privateVariable = DRange1d.NullRange; public DRange1d PrivateVariable { get { return _privateVariable; } set { _privateVariable = value; } } public DRange1d Property { get; set; } = DRange1d.NullRange; public static DRange1d StaticProperty { get; set; } = DRange1d.NullRange; public DPoint3d PointTest { get; set; }= DPoint3d.Zero; public void TestExtend() { StaticProperty = DRange1d.NullRange; DRange1d localVariable = DRange1d.NullRange; localVariable.Extend(72.5); Property.Extend(72.5); PrivateVariable.Extend(72.5); StaticProperty.Extend(72.5); Debug.Print("Extend Results:"); Debug.Print("localVariable: " + (localVariable.IsExactNull ? "Null": "Not Null")); Debug.Print("Property: " + (Property.IsExactNull ? "Null": "Not Null")); Debug.Print("PrivateVariable: " + (PrivateVariable.IsExactNull ? "Null": "Not Null")); Debug.Print("StaticProperty: " + (StaticProperty.IsExactNull ? "Null": "Not Null")); PointTest.Set(1,2,3); Debug.Print("Point Test: " + PointTest.X + " " + PointTest.Y + " " + PointTest.Z); } public void TestUnion() { StaticProperty = DRange1d.NullRange; DRange1d localVariable = DRange1d.NullRange; localVariable = DRange1d.FromUnion(localVariable, DRange1d.From(72.5)); Property = DRange1d.FromUnion(Property, DRange1d.From(72.5)); PrivateVariable = DRange1d.FromUnion(PrivateVariable, DRange1d.From(72.5)); StaticProperty = DRange1d.FromUnion(StaticProperty, DRange1d.From(72.5)); Debug.Print("Union Results:"); Debug.Print("localVariable: " + (localVariable.IsExactNull ? "Null": "Not Null")); Debug.Print("Property: " + (Property.IsExactNull ? "Null": "Not Null")); Debug.Print("PrivateVariable: " + (PrivateVariable.IsExactNull ? "Null": "Not Null")); Debug.Print("StaticProperty: " + (StaticProperty.IsExactNull ? "Null": "Not Null")); } }
Output:
Extend Results:localVariable: Not NullProperty: NullPrivateVariable: NullStaticProperty: NullPoint Test: 0 0 0
Union Results:localVariable: Not NullProperty: Not NullPrivateVariable: Not NullStaticProperty: Not Null
I will leave it at this and will need to be wary of this in the future.
As always, thanks for your input
Maury said:When calling extend on the property, it seems like the extend method is being called on the copy and not reflecting on the private variable.
That's probably the most important C# feature I did not mention in my previous answer (but is hidden in my note about to what struct instance a variable represent): When value type (e.g. struct) is returned by a method, its copy is returned. When struct copy is created, it's initialized to default values, so it's probably why .Equals method returns true for these structs and DRange1d.NullRange.
Regards,
Answer Verified By: Maury
One more note:
When you need to return original value type, not its copy, you can define property getter with ref (and it's not allowed to use setter in such case):
public ref DRange1d PublicRange { get { return ref this.myRange2; } }
But personally I treat it as antipattern, because it breaks encapsulation and immutability (it's possible to modify internal struct state from outside).
My question is whether it's really required to use properties when working with struct and whether it's really necessary. I see two (better and clearer in my opinion) alternatives:
Maury said: it probably does have something to do with the struct type being copied and how properties are handled
As is often the case, Stack Overflow yields some relevant comments.
Regards, Jon Summers LA Solutions