I am trying to write an Alias
class which enables me to:
int n = new Count(1);
That is, it encapsulates an int
in this case as a Count
, which gives some type safety and domain meaning, while it automatically converts back to the underlying type.
With non-nullable reference types, I have another issue. I cannot figure out how to handle both of these scenarios at the same time:
int someCount = new Count(1);
Count? nothing = null;
int? noCount = nothing;
This happens because I have types like this:
record Device(Count Cpu, Count? Cores); // Contrived example
Seems like the problem is I cannot overload an operator with both nullable and non-nullable version of the same type:
record Alias<T>(T Value)
{
public static implicit operator T(Alias a) => a.Value;
public static implicit operator T?(Alias? a) => null;
}
record Count : Alias<int> { /**/ }
The point is, if I have a null, I want it converted to null of the target type.
CodePudding user response:
If you don't have any Alias
es that wraps reference types, then I think the best thing to do here is to just limit T
here to struct
s. After that, T
, and T?
become distinct types, allowing you to create two operators:
record Alias<T>(T Value) where T: struct
{
public static implicit operator T?(Alias2<T>? a) => a?.Value;
public static implicit operator T(Alias2<T> a) => a.Value;
}
If you also need to wrap reference types as well, you could consider adding another Alias
type that works just for reference types:
record AliasClass<T>(T Value) where T: class
{
[return: NotNullIfNotNull("a")]
public static implicit operator T?(AliasClass<T>? a) => a?.Value;
}
record AliasStruct<T>(T Value) where T: struct
{
public static implicit operator T?(AliasStruct<T>? a) => a?.Value;
public static implicit operator T(AliasStruct<T> a) => a.Value;
}
Then you can have for example:
record Count(int Value) : AliasStruct<int>(Value) { /**/ }
record StringWrapper(string Value) : AliasClass<string>(Value) { /**/ }
CodePudding user response:
As commented, it's not possible to overload an operator with both nullable and non-nullable generic types.
I kind of solved it with extension methods:
public static class AliasClass
{
public static V? Unwrap<V, A>(this Alias<V, A>? a)
where A : Alias<V, A> where V : class => a?.Value;
}
public static class AliasStruct
{
public static V? Unwrap<V, A>(this Alias<V, A>? a)
where A : Alias<V, A> where V : struct => a?.Value;
}
[SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")]
public abstract record Alias<V, A> where A : Alias<V, A>
{
protected Alias(V value)
{
Value = value;
EnsureValid(Value);
}
public V Value { get; }
protected virtual void EnsureValid(V value) { }
public override sealed string ToString() => Value?.ToString() ?? "";
public static implicit operator V(Alias<V, A> a) => a.Value;
}
Usage:
int a = new Count(1);
int? n = new Count(2).Unwrap();
The symmetry, sadly, is broken. I can't find a way to implement Unwrap()
for the non-nullable case.