Behavior with Nullable Interface while implicit maybe-generation
LaszloLueck opened this issue · 7 comments
Currently if facing in an issue / behavior with the implicit Maybe-generation.
As for an example:
public interface ITest
{
}
private Maybe<ITest> CallMethod()
{
ITest? test = null;
return test;
}
The return test
resulted in an error cannot convert expression ITest? to return type Maybe<ITest>
.
Is there not an implicit conversion for nullable interfaces?
The above described problem is an easy example. In my code i receive (from Quartz.net) e.g. a Task<ITrigger?> and, also for async operations, the implicit conversion does not work.
Regards
Laszlo
What is working atm for me is to manual resolve the nullable.
As for an example:
public interface ITest
{
}
private Maybe<ITest> CallMethod()
{
ITest? test = null;
return test == null ? Maybe<ITest>.None : Maybe<ITest>.From(test);
}
In my opinion the main problem is, that for the current implementation of the implicit conversion, the Type ITest?
is another thing as ITest
so the conversion does not work.
I have written two static extension functions
public static Maybe<TOut> MaybeValue<TIn, TOut>([AllowNull] this TIn? value) where TIn : TOut
{
return value == null ? Maybe<TOut>.None : Maybe<TOut>.From(value);
}
public static TOut ResolveNullable<TIn, TOut>([AllowNull] this TIn? nullable, TOut alternative, Func<TIn, TOut, TOut> action) where TIn : class
{
return nullable
.MaybeValue<TIn, TIn>()
.Match(d => action.Invoke(d, alternative), () => alternative);
}
The first one resolves the nullable in a specific Maybe (as described in my posts above).
The second resolves a nullable object and let you decide what is the best result when null.
It´s not optimal (the first need in any case the input and return type, there is no autoresolve) and the second could be a little bit complecated but here are a view examples of how to use:
define an interface:
public interface ITest{}
Somewhere in your code;
ITest? x = null;
var foo = x.MaybeValue<ITest, ITest>();
foo is of type Maybe<ITest>
And for the second one (which used the MaybeValue method:
//ok, that is simple, x is null
StringValue? x = null;
//also cool, y is a StringValue object, but the property Value is null
StringBuilder? y = new StringValue();
//Now you can use:
var foo = y.ResolveNullable(string.Empty, (value, alternative) => value.Value ?? alternative)
The result is
a) alternative (string.Empty) when y is null
b) alternative (string.Empty) when y.Value is null
c) property value when a and b not null
if the nullable object is e.g. a DateTimeValue? (here the value is not a nullable) you can pass the inner alternative away. e.g.
var foo = d.Date.ResolveNullable(new DateTime(1970, 1, 1), (value, _) => value.Value)
The ResolveNullable is (for me) better readable syntactic sugar for StringValue?.Value??alternative
OK, that was easy...
The signature for MaybeValue
has changed to:
public static Maybe<TOut> MaybeValue<TOut>([AllowNull] this TOut? value)
{
return value == null ? Maybe<TOut>.None : Maybe<TOut>.From(value);
}
Because the In-Type is the same as the out-type but as nullable one.
So the using of that in ResolveNullable
is also a little bit different:
public static TOut ResolveNullable<TIn, TOut>([AllowNull] this TIn? nullable, TOut alternative, Func<TIn, TOut, TOut> action) where TIn : class
{
return nullable
.MaybeValue()
.Match(d => action.Invoke(d, alternative), () => alternative);
}
And the usage as example from above:
define an interface:
public interface ITest{}
Somewhere in your code;
ITest? x = null;
var foo = x.MaybeValue();
foo is of type Maybe<ITest>
This is not related to nullable/non-nullable reference types (?
reference types behave the same as non-?
reference types behind the scenes; ?
is just for compiler analysis). The issue is with interfaces -- the implicit conversion doesn't work for them.
In your example, instead of this:
return test == null ? Maybe<ITest>.None : Maybe<ITest>.From(test);
just use:
return Maybe.From(test);
or
return Maybe.From<ITest>(test);
Hi Vladimir,
thanks for your response.
But this, with a nullable interface want work!
return Maybe.From(test);
And this
return Maybe<ITest>.From(test);
also did not working. The result ist a Maybe<ITest?> e.g. you have a nullable maybe (and thats definitely not what you want).
As you can see below (a screenshot of my ide):
ITest is a public interface ITest{};
You can put the null value ITest in your Maybe.From with the result of a nullable Maybe.
If you resolve this back to a concrete value, you receive a Nullable ITest.
My goal is, to eleminate the nullables, also from an (possible nullable) interface, that was the reason of my question.
For that i´ve build some helper methods to avoid the usage of ?
or ??
as often as necessary.
Here are the both helper methods as i used it in my project (one with none Func<> and one with none Action<>).
public static TOut ResolveNullable<TIn, TOut>([AllowNull] this TIn? nullable, [DisallowNull] TOut alternative, Func<TIn, TOut, TOut> action)
{
return nullable is not null ? action.Invoke(nullable, alternative) : alternative;
}
public static TOut ResolveNullable<TIn, TOut>([AllowNull] this TIn? nullable, [DisallowNull] TOut alternative, Func<TIn, TOut, TOut> some, Func<TOut, TOut> none)
{
return nullable is not null ? some.Invoke(nullable, alternative) : none.Invoke(alternative);
}
And here is how i use this:
public ITest Foo()
{
ITest? nullable = null;
ITest alternative = new Test();
return nullable.ResolveNullable(alternative, (maybeValue, _) => maybeValue);
}
Regards
Laszlo
Sorry for late replies.
You can have it both ways:
Maybe<ITest> maybe = Maybe.From(test);
or
Maybe<ITest?> maybe = Maybe.From(test);
The compiler will agree with either option.
Your suggestion is to add extensions for T?
, in addition to Maybe<T>
? If so, I don't think it's a good idea, from the point of keeping the library focused on the one way of doing things. T?
is an alternative to Maybe<T>
. It makes sense to support the conversion from one to the other, but not re-implement the existing extension methods on top of both.
Hi Vladimir,
thanks for your response.
I think you´re right. It is not the best idea to wrap an extension like maybe over a low level nullable handling like in c# 8.0 (which is exactly designed for).
As i coded in Scala, there was the best practice to handle nulls as None<T>
or not nulls as Some<T>
to avoid nulls from your code.
I leave my function extensions to remove the nullables in my project. This is primarily syntactic sugar for me, to avoid the handling with ??
and ?
.
Thanks for your patience,
Regards,
Laszlo