问题
I'm trying to create an extension method to do this:
enum AlphaBet { A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z }
IEnumerable<AlphaBet> rangeCtoG = AlphaBet.C.RangeToInc(AlphaBet.G);
But this won't compile (as TEnum
is generic):
public static IEnumerable<TEnum> RangeToInc<TEnum>(this TEnum from, TEnum to)
where TEnum : struct, IComparable, IFormattable, IConvertible //
{
for (; from <= to; from++)
yield return from;
}
So I turned to expressions:
public delegate void MyFunc<T>(ref T arg); // allow PostIncrementAssign to change the input parameter
public static IEnumerable<TEnum> RangeToInc<TEnum>(this TEnum from, TEnum to)
where TEnum : struct, IComparable, IFormattable, IConvertible //
{
var fromRefParamExpr = Expression.Parameter(typeof(TEnum).MakeByRefType(), "fromParam");
var incrementExpr = Expression.PostIncrementAssign(fromRefParamExpr);
var increment = Expression.Lambda<MyFunc<TEnum>>(incrementExpr, fromRefParamExpr).Compile();
var fromParamExpr = Expression.Parameter(typeof(TEnum), "fromParam");
var toParamExpr = Expression.Parameter(typeof(TEnum), "toParam");
var lessThanOrEqualExpr = Expression.LessThanOrEqual(fromParamExpr, toParamExpr);
var lessThanOrEqual = Expression.Lambda<Func<TEnum, TEnum, bool>>(
lessThanOrEqualExpr, toParamExpr, fromParamExpr).Compile();
for (; lessThanOrEqual(to, from); increment(ref from))
yield return from;
}
It works great with integers, but not with enums: the line Expression.PostIncrementAssign(fromRefParamExpr)
fails with exception:
System.InvalidOperationException:
'The unary operator PostIncrementAssign is not defined for the type
'...+AlphaBet'.'
Which is quite suprising - isn't an enum
a numeric type? what am I supposed to do? cast back and forth enum
⇔ int
?
回答1:
maybe this is can help you..
public static IEnumerable<TEnum> RangeToInc<TEnum>(this TEnum from, TEnum to) where
TEnum : struct, IComparable, IFormattable, IConvertible
{
return Enum.GetValues(typeof(TEnum)).Cast<TEnum>().Where(
a => a.CompareTo(from) >= 0
&& a.CompareTo(to) <= 0).ToArray(); //Actually it does not need ToArray
}
Slightly more expandable
public static IEnumerable<TEnum> RangeToInc<TEnum>(this TEnum from, TEnum to) where
TEnum : struct, IComparable, IFormattable, IConvertible
{
if (typeof(TEnum).IsEnum)
return Enum.GetValues(typeof(TEnum)).Cast<TEnum>().Where(
a => a.CompareTo(from) >= 0
&& a.CompareTo(to) <= 0);
else if (typeof(TEnum).IsNumericType())
{
int start = Convert.ToInt32(from);
int count = Convert.ToInt32(to) - start;
return Enumerable.Range(start, count + 1).Cast<TEnum>();
}
throw new NotImplementedException();
}
public static bool IsNumericType(this Type type)
{
switch (Type.GetTypeCode(type))
{
case TypeCode.Byte:
case TypeCode.SByte:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.Decimal:
case TypeCode.Double:
case TypeCode.Single:
return true;
default:
return false;
}
}
using
IEnumerable<DayOfWeek> enumRange = DayOfWeek.Sunday.RangeToInc(DayOfWeek.Wednesday);
foreach (var item in enumRange)
{
Console.Write(item + " ");
//
}
Console.WriteLine();
int a = 4;
var intRange = a.RangeToInc(10);
foreach (var item in intRange)
{
Console.Write(item + " ");
}
//output
// Sunday Monday Tuesday Wednesday
// 4 5 6 7 8 9 10
回答2:
Sadly you'll have to cast back and forth... What you can do is cache the resulting expression tree and use the internal .NET runtime for this:
public static class EnumerableHelper
{
public static IEnumerable<TEnum> RangeToInc<TEnum>(this TEnum from, TEnum to)
where TEnum : struct, IComparable, IFormattable, IConvertible
{
var cmp = Comparer<TEnum>.Default;
while (cmp.Compare(from, to) <= 0)
{
yield return from;
from = EnumerableHelperImpl<TEnum>.Increment(from);
}
}
private static class EnumerableHelperImpl<TEnum>
{
public static readonly Func<TEnum, TEnum> Increment;
static EnumerableHelperImpl()
{
var par = Expression.Parameter(typeof(TEnum));
Expression body = typeof(TEnum).IsEnum ?
Expression.Convert(Expression.Increment(Expression.Convert(par, Enum.GetUnderlyingType(typeof(TEnum)))), typeof(TEnum)) :
Expression.Increment(par);
Increment = Expression.Lambda<Func<TEnum, TEnum>>(body, par).Compile();
}
}
}
and then:
IEnumerable<AlphaBet> rangeCtoG = AlphaBet.C.RangeToInc(AlphaBet.G);
IEnumerable<int> range2 = 5.RangeToInc(8);
Note that you can even create ranges betweeen int
and not only between enum
. The "trick" here is to use a private EnumerableHelperImpl<TEnum>
, that will be different depending on the TEnum
. The .NET will generate those EnumerableHelperImpl<TEnum>
based on the TEnum
types used and will cache them. The code will work even with non-int
enum
s.
来源:https://stackoverflow.com/questions/44309139/how-to-apply-generic-expression-trees-code-that-works-well-on-integers-to-enums