Ability to write reference-like custom value types #7759
Unanswered
jaigak
asked this question in
Language Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
When projecting a foreign object model like COM/WinRT or just wanting to write bindings for a native library, wrapping the types in a managed environment, especially ones which exhibit reference semantics, can be a challenging task.
While technically it is possible to create a struct containing a pointer internally, a number of limitations in: C# and the .NET runtime make that just not as nice as a class wrapper.
But a class wrapper also increases complexity often. For example, users expect that when you take an object, pass it around and then get it back, it is still the same object - meaning ReferenceEquals returns true. While you could override Equals and operator==, generic or dealing-with-boxed-objects code that used ReferenceEquals breaks. In this case having different objects wrap the native pointer is really just an implementation detail of the interop layer. Of course, real bindings often cache the mapping between the native pointer and the managed object but such a cache is superfluous unless actual, additional state needs to be added (it is also true that .NET won't play nicely with types expecting unique ownership, but that's a different issue).
Anyway, here are some features that I think C# and .NET should have.
Being able to have a constructor for a value type that only takes nulls. That is, I can only pass the null literal into it. Something like C++ std::nullptr_t can be done for this, where the null literal itself has a type.
Being able to override ReferenceEquals for a value type.
Being able to have a custom copy constructor. Less important for unique ownership but more so for shared ownership with reference counting.
Being able to have a destructor. While IDisposable could be used, it shows the difference and is generally annoying. And it's not like IDisposable.Dispose is actually disposing of anything - it is just decreasing a reference count in case of an object with shared ownership, which may or may not dispose the object. It would be a mistake to map such a construct to IDisposable.Dispose, which is about explicitly disposing the object without the object itself becoming non-existent.
Users' thinking after using my library: "Why would a class representing file paths be IDisposable?".
Now I understand that making disposal automatic when used as a class member is not very trivial for regular .NET types because the ownership is not clear. But I think calling the destructor (not Dispose) when the object is finalised is very much feasible.
Ability to customise casting. I know that .NET 5 had introduced
IDynamicInterfaceCastable
but it only works for reference types.I think that is all for now. Now some people might be too concerned about features like this being abused and hence providing unintuitive behaviour. Some of the features like having a destructor and copy constructor are useful even when you are not writing a type for interop. But others like overriding ReferenceEquals or customising casting is certainly not for everything. Maybe there should be a new kind of struct - I am calling it interop struct.
Beta Was this translation helpful? Give feedback.
All reactions