There are extension methods for Nullable<T>
like below.
using System;
using System.Runtime.CompilerServices;
namespace DoNotationish
{
public static class NullableExtensions
{
public static U? Select<T, U>(this T? nullableValue, Func<T, U> f)
where T : struct
where U : struct
{
if (!nullableValue.HasValue) return null;
return f(nullableValue.Value);
}
public static V? SelectMany<T, U, V>(this T? nullableValue, Func<T, U?> bind, Func<T, U, V> f)
where T : struct
where U : struct
where V : struct
{
if (!nullableValue.HasValue) return null;
T value = nullableValue.Value;
U? bindValue = bind(value);
if (!bindValue.HasValue) return null;
return f(value, bindValue.Value);
}
}
}
This allows Nullable<T>
to be used in query syntax. The following tests will pass.
[Test]
public void Test1()
{
int? nv1 = 5;
int? nv2 = 3;
var q = from v1 in nv1
from v2 in nv2
select v1 + v2;
Assert.AreEqual(8, q);
}
[Test]
public void Test2()
{
int? nv1 = null;
int? nv2 = 3;
var q = from v1 in nv1
from v2 in nv2
select v1 + v2;
Assert.IsNull(q);
}
However, if you try to chain 3 or more, it will be treated as an anonymous type and will not compile.
[Test]
public void Test3()
{
int? nv1 = 5;
int? nv2 = 3;
int? nv3 = 8;
var q = from v1 in nv1
from v2 in nv2 // Error CS0453: anonymous type is not struct
from v3 in nv3
select v1 + v2 + v3;
Assert.AreEqual(16, q);
}
You can work around this issue by manually specifying to use ValueTuple
as below, but this is ugly.
[Test]
public void Test3_()
{
int? nv1 = 5;
int? nv2 = 3;
int? nv3 = 8;
var q = from v1 in nv1
from v2 in nv2
select (v1, v2) into temp // ugly
from v3 in nv3
select temp.v1 + temp.v2 + v3; // ugly
Assert.AreEqual(16, q);
}
These simplified examples can be solved simply by using the +
operator: var q = nv1 + nv2 + nv3;
However, you would find it more convenient to work with user-defined structs if you could write it fluently. Is there any good way?
Think about how the compiler would turn the query expression into SelectMany
calls. It would turn it into something like:
var q =
nv1.SelectMany(x =>
nv2.SelectMany(x => nv3, (v2, v3) => new { v2, v3 }),
(v1, v2v3) => v1 + v2v3.v2 + v2v3.v3);
Note how the V
of the second SelectMany
call is inferred to be an anonymous class, which is a reference type, and doesn't fit the constraint of : struct
.
Note that it specifically uses an anonymous class, rather than a ValueTuple
( (v2, v3) => (v2, v3)
). This is specified in the language spec :
A query expression with a second from clause followed by something other than a select clause:
from x1 in e1 from x2 in e2...
is translated into
from * in ( e1 ). SelectMany( x1 => e2, ( x1, x2 ) => new { x1, x2 } )...
So unfortunately, you can't do anything about it. You can try forking the Roslyn compiler to make it compile to create a ValueTuple
instead, but technically that's not "C#" anymore.
OTOH, this idea could work if you write your own Nullable<T>
type, and not constrain T
to a value type, but I'm not sure that's worth it.
Let's take a look at this query
from a in source1
from b in source2
from c in source3
from d in source4
// etc
select selector // how is it possible that a, b, c, d available in selector?
Such queries will be compiled as a chain of SelectMany calls
SelectMany(IEnumerable<TSource> source,
Func<TSource, IEnumerable<TCollection>> collectionSelector,
Func<TSource, TCollection, TResult> resultSelector)
As you can see, it can accept only two arguments in result selector - of one of source collection type and one of second collection type returned by selector. So the only way to pass more than two arguments down the chain (so that all arguments eventually will arrive in the last result selector) is by creating anonymous types. And this is how it looks like:
source1
.SelectMany(a => source2, (a, b) => new { a, b })
.SelectMany(x1 => source3, (x1, c) => new { x1, c })
.SelectMany(x2 => source4, (x2, d) => selector(x2.x1.a, x2.x1.b, x2.c, d));
Again, the result selector limited with two input arguments. So for your passing Test1 and Test2 anonymous type is not created, because both arguments can be passed to the result selector. But Test3 requires three arguments for the result selector and an intermediate anonymous type is created for that.
You cannot make your extension method to accept both nullable structs and generated anonymous types (which are reference types). I would suggest you create domain-specific extension methods Bind and Map . Pair of these methods will be aligned with functional programming domain much more than from v1 in nv1
queries:
public static U? Bind<T, U>(this T? maybeValue, Func<T, U?> binder)
where T : struct
where U : struct
=> maybeValue.HasValue ? binder(maybeValue.Value) : (U?)null;
public static U? Map<T, U>(this T? maybeValue, Func<T, U> mapper)
where T : struct
where U : struct
=> maybeValue.HasValue ? mapper(maybeValue.Value) : (U?)null;
And usage
nv1.Bind(v1 => nv2.Bind(v2 => nv3.Map(v3 => v1 + v2 + v3)))
.Map(x => x * 2) // eg
There are extension methods for Nullable<T>
like below.
using System;
using System.Runtime.CompilerServices;
namespace DoNotationish
{
public static class NullableExtensions
{
public static U? Select<T, U>(this T? nullableValue, Func<T, U> f)
where T : struct
where U : struct
{
if (!nullableValue.HasValue) return null;
return f(nullableValue.Value);
}
public static V? SelectMany<T, U, V>(this T? nullableValue, Func<T, U?> bind, Func<T, U, V> f)
where T : struct
where U : struct
where V : struct
{
if (!nullableValue.HasValue) return null;
T value = nullableValue.Value;
U? bindValue = bind(value);
if (!bindValue.HasValue) return null;
return f(value, bindValue.Value);
}
}
}
This allows Nullable<T>
to be used in query syntax. The following tests will pass.
[Test]
public void Test1()
{
int? nv1 = 5;
int? nv2 = 3;
var q = from v1 in nv1
from v2 in nv2
select v1 + v2;
Assert.AreEqual(8, q);
}
[Test]
public void Test2()
{
int? nv1 = null;
int? nv2 = 3;
var q = from v1 in nv1
from v2 in nv2
select v1 + v2;
Assert.IsNull(q);
}
However, if you try to chain 3 or more, it will be treated as an anonymous type and will not compile.
[Test]
public void Test3()
{
int? nv1 = 5;
int? nv2 = 3;
int? nv3 = 8;
var q = from v1 in nv1
from v2 in nv2 // Error CS0453: anonymous type is not struct
from v3 in nv3
select v1 + v2 + v3;
Assert.AreEqual(16, q);
}
You can work around this issue by manually specifying to use ValueTuple
as below, but this is ugly.
[Test]
public void Test3_()
{
int? nv1 = 5;
int? nv2 = 3;
int? nv3 = 8;
var q = from v1 in nv1
from v2 in nv2
select (v1, v2) into temp // ugly
from v3 in nv3
select temp.v1 + temp.v2 + v3; // ugly
Assert.AreEqual(16, q);
}
These simplified examples can be solved simply by using the +
operator: var q = nv1 + nv2 + nv3;
However, you would find it more convenient to work with user-defined structs if you could write it fluently. Is there any good way?
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.