[英]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...else
和switch...case
。
interface
來避免if...else
。 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
方法返回int
和double
之間的比較,將結果作為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.