繁体   English   中英

c#lambda函数和运行时类型转换

[英]c# lambda functions and runtime type conversion

我正在创建以下函数(psudo代码),并想知道是否有更有效的方式来编写它。

正在从文件中读取数据,并且数据类型由用户作为配置提供。

Func<object,object,object> GetFunction(string op, string dataType) {
    if (op == "sum") {
        if (dataType == 'int') return (a,b) => (int)a + (int) b;
        if (dataType == 'double') return (a,b) => (double)a + (double)b;
        // ... other numeric data types
    }
    elseif (op == "max") {
        if (dataType == 'int') return (a,b) => Math.Max((int)a + (int) b);
        if (dataType == 'double') return (a,b) => Math.Max((double)a + (double)b);

    }
    elseif (op == "min") {
    // ...
    }
}

编辑:我试图像这样实现它,但编译器给出错误“错误CS0118:'dataType'是一个变量,但我尝试这样做时使用像一个类型”。

Func<object,object,object> GetFunction(string op, Type dataType) {
    if (op == "sum")  return (a,b) => (dataType)a + (dataType) b;
    if (op == "max")  return (a,b) => Math.Max((dataType)a + (dataType) b);
    if (op == "min")  ...
}

这是你用C#快速遇到噩梦的少数几个地方之一,因为这里不可能使用泛型(不能在“T”类型上使用算术运算符)。 在我看来,最好的解决方案是在这种情况下使用generic类型。 它可能不是很快,但它获得最短的源代码。

在这种情况下,你应该尝试类似的东西:

    public delegate T OperationDelegate<T>(dynamic a, dynamic b);

    public OperationDelegate<T> GetFunction<T>(string op)
    {
        if (op == "sum")
        {
            return (a, b) => a + b;

        }
        else
        if(op == "max") {
            return (a, b) => Math.Max(a, b);

        }

        throw new InvalidOperationException();
    }

// Use as: 
var myOp = GetFunction<double>("sum");
double result = myOp(1.0, 2.0);

你甚至可以以某种方式摆脱GetFunction调用的类型参数。

所有的想法都是为了避免if...elseswitch...case

  1. 对于参数类型,我们可以使用interface来避免if...else
  2. 对于action名称,我使用switch...case ,不是很完美。

其他人可以继续我的想法并编辑这个答案:

class Program
{
    static void Main(string[] args)
    {
        //c# will automatically pick best match by give parameter.
        Console.WriteLine("1 + 2 = " + Numeric.Instance.Calculate("Sum", 1, 2));
        Console.WriteLine("Max(1, 2) = " + Numeric.Instance.Calculate("Max", 1.5, 2.1));

        Console.ReadLine();
    }

    interface INumeric<T>
    {
        T Sum(T a, T b);
        T Max(T a, T b);
        T Min(T a, T b);


        ///// <summary>
        ///// This works only in .NET core 3.0, c# 8.0.
        ///// </summary>
        ///// <param name="method"></param>
        ///// <param name="a"></param>
        ///// <param name="b"></param>
        ///// <returns></returns>
        //public virtual T Calculate(string method, T a, T b)
        //{
        //    switch (method)
        //    {
        //        case "Sum":
        //            {
        //                return Sum(a, b);
        //            }
        //        case "Max":
        //            {
        //                return Max(a, b);
        //            }
        //        case "Min":
        //            {
        //                return Min(a, b);
        //            }
        //        default:
        //            {
        //                throw new NotImplementedException($"Method '{method}' is not supported.");
        //            }
        //    }
        //}
    }

    struct Numeric :
        INumeric<int>,
        INumeric<double>,
        INumeric<long>
    {
        public double Sum(double a, double b) => a + b;

        public double Max(double a, double b) => Math.Max(a, b);

        public double Min(double a, double b) => Math.Min(a, b);

        public int Sum(int a, int b) => a + b;

        public int Max(int a, int b) => Math.Max(a, b);

        public int Min(int a, int b) => Math.Min(a, b);

        public long Sum(long a, long b) => a + b;

        public long Max(long a, long b) => Math.Max(a, b);

        public long Min(long a, long b) => Math.Min(a, b);

        public int Calculate(string method, int a, int b)
        {
            switch (method)
            {
                case "Sum":
                    {
                        return Sum(a, b);
                    }
                case "Max":
                    {
                        return Max(a, b);
                    }
                case "Min":
                    {
                        return Min(a, b);
                    }
                default:
                    {
                        throw new NotImplementedException($"Method '{method}' is not supported.");
                    }
            }
        }

        public double Calculate(string method, double a, double b)
        {
            switch (method)
            {
                case "Sum":
                    {
                        return Sum(a, b);
                    }
                case "Max":
                    {
                        return Max(a, b);
                    }
                case "Min":
                    {
                        return Min(a, b);
                    }
                default:
                    {
                        throw new NotImplementedException($"Method '{method}' is not supported.");
                    }
            }
        }

        public long Calculate(string method, long a, long b)
        {
            switch (method)
            {
                case "Sum":
                    {
                        return Sum(a, b);
                    }
                case "Max":
                    {
                        return Max(a, b);
                    }
                case "Min":
                    {
                        return Min(a, b);
                    }
                default:
                    {
                        throw new NotImplementedException($"Method '{method}' is not supported.");
                    }
            }
        }

        public static Numeric Instance = new Numeric();

    }

}

我担心你将不得不重复代码,因为当涉及算术运算符时,通用解决方案是不可能的。 但是通过使用switch语句,您可以获得比使用if语句更清晰的代码

object typedObject = Activator.CreateInstance(dataType);
switch (typedObject) {
    case int i:
        switch (op) {
            case "sum":
                return (a, b) => (int)a + (int)b;
            case "max":
                return (a, b) => Math.Max((int)a , (int)b);
            default:
                return null;
        }
    case double d:
        switch (op) {
            case "sum":
                return (a, b) => (double)a + (double)b;
            case "max":
                return (a, b) => Math.Max((double)a, (double)b);
            default:
                return null;
        }
    default:
        return null;
}

诀窍是从Type变量创建一个虚拟对象,并将其用于外部开关中的模式匹配。

值类型的通用约束可能很棘手, ValueType不允许作为约束并且没有用,因为,例如, Point也是ValueType ,您不需要或不需要特定类型。
所以,我建议你这个疯狂的方法。 不供公众使用。

可以执行的操作类型由枚举器定义,因此您不能错误地执行操作,并且如果需要,可以轻松扩展而不会破坏任何操作。

接受的值类型受本地函数的结果限制,该函数保存支持的类型。

例如,要让Max方法返回intdouble之间的比较,将结果作为int返回,可以像这样调用它:

int max = GetFunction<int, double, int>(OperationType.Max)(100, 120.5d);
// max = 120

或者,获取该函数,然后在需要时添加参数:

var operationMultiply = GetFunction<int, double, int>(OperationType.Multiply);
var result = operationMultiply(100, 120.5d);
// result = 12050

Func<T1, T2, TR> GetFunction<T1, T2, TR>(OperationType op) 
    where T1: struct where T2: struct where TR: struct
{
    if (!IsSupported(typeof(T1), typeof(T2))) {
        throw new NotSupportedException($"Operation on {typeof(T1)} and {typeof(T2)} is not supported.");
    }

    switch (op)
    {
        default:
        case OperationType.Sum:
                return (a, b) => (TR)((dynamic)a + b);
        case OperationType.Subtract:
            return (a, b) => (TR)((dynamic)a - b);
        case OperationType.Multiply:
            return (a, b) => (TR)((dynamic)a * (dynamic)b);
        case OperationType.Max:
            return (a, b) => (TR)Math.Max((dynamic)a, (dynamic)b);
    }
    bool IsSupported(Type t1, Type t2)
    {
        Type[] suppTypes = new[] { typeof(int), typeof(decimal), typeof(double), typeof(float) };
        if (!suppTypes.Contains(typeof(T1))) return false;
        if (!suppTypes.Contains(typeof(T2))) return false;
        return true;
    }
}

public enum OperationType
{
    Sum = 0, 
    Subtract,
    Multiply,
    Divide,
    Min, 
    Max,
    //Other supported operations
}

您可以使用一些预设的Dictionary变量将字符串映射到Type和operator,然后手动构建Expression ,并编译它们:

static Dictionary<string, Type> TypeMap = new Dictionary<string, Type> {
    { "int", typeof(Int32) },
    { "double", typeof(double) }
};
static Dictionary<string, (ExpressionType opType, Type methodType, string methodName)> OpMap = new Dictionary<string, (ExpressionType, Type, string)> {
    { "sum", (ExpressionType.Add, null, "") },
    { "difference", (ExpressionType.Subtract, null, "") },
    { "multiply", (ExpressionType.Multiply, null, "") },
    { "max", (ExpressionType.Call, typeof(Math), "Max") },
    { "min", (ExpressionType.Call, typeof(Math), "Min") },
};

Func<object, object, object> MakeFunction(string op, string dataType) {
    var parmType = TypeMap[dataType];
    var parma = Expression.Parameter(typeof(object), "a");
    var parmb = Expression.Parameter(typeof(object), "b");
    var changeTypeMI = typeof(Convert).GetMethod("ChangeType", new[] { typeof(object), typeof(Type) });
    var exprParmType = Expression.Constant(parmType);
    var lefta = Expression.Convert(Expression.Call(changeTypeMI, parma, exprParmType), parmType);
    var rightb = Expression.Convert(Expression.Call(changeTypeMI, parmb, exprParmType), parmType);
    Expression expr = null;
    var opTuple = OpMap[op];
    switch (opTuple.opType) {
        case ExpressionType.Call:
            var mi = opTuple.methodType.GetMethod(opTuple.methodName, new[] { parmType, parmType });
            expr = Expression.Call(mi, lefta, rightb);
            break;
        default:
            expr = Expression.MakeBinary(opTuple.opType, lefta, rightb);
            break;
    }
    var body = Expression.Convert(expr, typeof(object));
    return Expression.Lambda<Func<object, object, object>>(body, parma, parmb).Compile();
}

注意:我添加了一个对Convert.ChangeType的调用来处理,如果你将一个int传递给你为“double”所做的函数。 如果您只打算传入相应的类型,则可以将其删除并留下Expression.Convert强制转换。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM