簡體   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