[英]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.