Home > OS >  Is it possible in C# to write an implicit conversion operator for both nullable and non-nullable val
Is it possible in C# to write an implicit conversion operator for both nullable and non-nullable val

Time:09-07

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 Aliases that wraps reference types, then I think the best thing to do here is to just limit T here to structs. 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.

  • Related