How to apply generic expression-trees code that works well on integers, to enums (e.g. how to make Expression.PostIncrementAssign() work on enums?)?

你离开我真会死。 提交于 2020-01-05 04:44:05

问题


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 enumint?


回答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 enums.



来源:https://stackoverflow.com/questions/44309139/how-to-apply-generic-expression-trees-code-that-works-well-on-integers-to-enums

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!