我正在编写一个类,它对C#中的每个原始数字类型执行基本相同类型的计算。虽然实际计算更复杂,但可以将其视为计算多个值的平均值的方法,例如
class Calc
{
public int Count { get; private set; }
public int Total { get; private set; }
public int Average { get { return Count / Total; } }
public int AddDataPoint(int data)
{
Total += data;
Count++;
}
}
现在支持double,float和其他定义operator +和operator /的类的相同操作,我首先想到的只是使用泛型:
class Calc<T>
{
public T Count { get; private set; }
public T Total { get; private set; }
public T Average { get { return Count / Total; } }
public T AddDataPoint(T data)
{
Total += data;
Count++;
}
}
不幸的是,C#无法确定T是否支持运算符+和/所以不编译上述代码段。我的下一个想法是将T限制为支持这些运算符的类型,但我的初步研究表明无法做到这一点。
当然可以在实现自定义界面的类中打包我想要支持的每种类型,例如IMath和限制T,但这个代码将被调用很多次,我想避免拳击开销。
在没有代码重复的情况下,是否有一种优雅而有效的方法来解决这个问题?
答案 0 :(得分:14)
我最终使用了表达方式,这是Marc Gravell概述的一种方法,我通过以下链接发现了spinon的评论。
https://jonskeet.uk/csharp/miscutil/usage/genericoperators.html
答案 1 :(得分:4)
(对不起,如果我今天发布,但我正在寻找一个放置这段代码的地方,这个问题似乎很完美)
作为Gravell文章的延伸:
public static class Add<T>
{
public static readonly Func<T, T, T> Do;
static Add()
{
var par1 = Expression.Parameter(typeof(T));
var par2 = Expression.Parameter(typeof(T));
var add = Expression.Add(par1, par2);
Do = Expression.Lambda<Func<T, T, T>>(add, par1, par2).Compile();
}
}
你可以像使用它一样:
int sum = Add<int>.Do(x, y);
优点是我们使用.NET的类型系统来保护Add
的各种“变体”,并在必要时创建新的变体。因此,当您第一次拨打Add<int>.Do(...)
时,Expression
将被构建,但如果您再次呼叫它,Add<int>
将已完全初始化。
在一些简单的基准测试中,它比直接添加慢2倍。我觉得这很好。啊......它与重新定义operator+
的对象兼容。显然,建立其他业务很容易。
来自Meirion Hughes的补充
可以使用元编码扩展方法,以便处理T1
操作 T2
的案例。例如,如果T1
是一个数字,则需要先在T2 == double
之前将其转换为operator *
,然后将其转换回来。如果T1
为Foo
且Foo
有运算符与T2 == double
相乘,则可以省略转换。 try
,catch
是必要的,因为这是检查T operator *(T, double)
是否存在的最简单方法。
public static class Scale<T>
{
public static Func<T, double, T> Do { get; private set; }
static Scale()
{
var par1 = Expression.Parameter(typeof(T));
var par2 = Expression.Parameter(typeof(double));
try
{
Do = Expression
.Lambda<Func<T, double, T>>(
Expression.Multiply(par1, par2),
par1, par2)
.Compile();
}
catch
{
Do = Expression
.Lambda<Func<T, double, T>>(
Expression.Convert(
Expression.Multiply(
Expression.Convert(par1, typeof (double)),
par2),
typeof(T)),
par1, par2)
.Compile();
}
}
}
答案 2 :(得分:2)
在C#4.0中有一种使用动态的方法,它显然并不完美,但它可以为此事带来新的亮点。
答案 3 :(得分:1)
我发现了另一种有趣的方法,它比我最初使用的表达式树解决方案更容易编码和调试:
http://www.codeproject.com/KB/cs/genericnumerics.aspx
此解决方案以有趣的方式使用泛型类型约束,以确保支持所有必需的操作,但不引入任何装箱或虚拟方法调用。