.NET 泛型中解决运算符重载约束的方法

39

如果我想要一个通用方法,只接受已重载运算符的类型,例如减法运算符,那我该怎么做?我试过使用接口作为约束条件,但接口无法进行运算符重载。

如何最好地实现这个方法?


1
你有使用这个的例子吗?我想不到任何有用的地方。 - Mitch Wheat
正如您所发现的那样,无法在接口上定义静态方法,因此您无法将其用作泛型方法的约束条件。 这里是一个有些复杂的解决方法: http://www.codeproject.com/KB/cs/genericnumerics.aspx 如果您使用的是.NET 3.5,则还可以通过LINQ表达式树来完成,如下所示: http://rogeralsing.com/2008/02/27/linq-expressions-calculating-with-generics/ - Ben Hoffstein
一个通用的“Sum”方法将是一个简单的例子。 T Sum<T>(IEnumerable<T> sequence); //其中T具有“+”运算符 - blackwing
请注意,在Roger的博客中,我们讨论和对比了这两种实现(它们非常相似) - 得出结论是MiscUtil代码(先前链接)更加发达。但它们使用相同的基本方法。 - Marc Gravell
可能是[定义一个实现+运算符的泛型]的重复问题。(https://dev59.com/QHA65IYBdhLWcg3w2iip) - Timwi
4个回答

54

没有直接的答案;运算符是静态的,不能在约束中表达 - 现有的原语也没有实现任何特定的接口(与 IComparable[<T>] 相比,后者可以用于模拟大于/小于)。

然而,如果您只想让它工作,在 .NET 3.5 中有一些选择...

我已经组合了一个库这里,可以有效且简单地访问泛型运算符,例如:

T result = Operator.Add(first, second); // implicit <T>; here

它可以作为MiscUtil的一部分下载。

此外,在C# 4.0中,通过dynamic也变得可能:

static T Add<T>(T x, T y) {
    dynamic dx = x, dy = y;
    return dx + dy;
}

我曾经也有一个.NET 2.0版本,但那个版本的测试较少。另一个选项是创建一个界面,例如:

interface ICalc<T>
{
    T Add(T,T)() 
    T Subtract(T,T)()
} 

等等,但是你需要通过所有的方法传递一个ICalc<T>;,这会变得混乱。


1
我喜欢在.NET 4.0中使用动态语言,这确实使得很多事情变得更加容易。但是需要指出的是,使用动态语言会对性能产生影响,因为它需要在运行时执行更多的工作。我很想知道它对性能有多大的影响,这需要进行一些基准测试。 - Martin Sherburn
基准测试已完成,请从此处尝试代码:http://social.msdn.microsoft.com/Forums/en-US/vs2010ctpvbcs/thread/287db1b9-c135-40bc-a1c5-a8a51efbfc65 - Marc Gravell
我试用了你的.NET 3.5库,我有一个问题:为什么以下代码行不起作用:MiscUtil.Operator.Add("A", "B");。在我的理解中,它应该返回"AB"。 - Malki
1
@Malki - 嗯,我们可以添加它,但那不是一个真正的算术运算。严格来说,它并不是一个定义好的运算符 - 目前是编译器(而不是类型本身)为字符串提供+的这种含义... - Marc Gravell
2
如果最终会转换为动态语言,使用泛型是否有意义?为什么不将参数和返回代码也设为动态的呢?以这种方式混合使用泛型和动态语言是否具有优势? - 9a3eedi

10

我发现IL实际上可以很好地处理这个问题。例如:

ldarg.0
ldarg.1
add
ret

只要指定了原始类型,通用方法编译后将可以正常运行。如果可能的话,可以扩展其功能以在非原始类型上调用操作函数。

请参见此处


0
您可以通过使用委托来解决这个问题,而不是使用接口的约束。
public class Example
{
    public static T Add<T>(T left, T right, Func<T, T, T> addFunc) =>
        addFunc(left, right);
}

定义一个接受委托作为参数的方法,并按以下方式使用它。
var result = Example.Add(10, 20, (x, y) => x + y);

-1

我经常使用从互联网上盗取的一段代码来实现这个功能。它寻找或构建基本算术运算符IL。所有操作都在一个泛型类Operation<T>中完成,你只需要将所需的操作分配给一个委托即可,例如add = Operation<double>.Add

使用方法如下:

public struct MyPoint
{
    public readonly double x, y;
    public MyPoint(double x, double y) { this.x=x; this.y=y; }
    // User types must have defined operators
    public static MyPoint operator+(MyPoint a, MyPoint b)
    {
        return new MyPoint(a.x+b.x, a.y+b.y);
    }
}
class Program
{
    // Sample generic method using Operation<T>
    public static T DoubleIt<T>(T a)
    {
        Func<T, T, T> add=Operation<T>.Add;
        return add(a, a);
    }

    // Example of using generic math
    static void Main(string[] args)
    {
        var x=DoubleIt(1);              //add integers, x=2
        var y=DoubleIt(Math.PI);        //add doubles, y=6.2831853071795862
        MyPoint P=new MyPoint(x, y);
        var Q=DoubleIt(P);              //add user types, Q=(4.0,12.566370614359172)

        var s=DoubleIt("ABC");          //concatenate strings, s="ABCABC"
    }
}

Operation<T> 源代码由 paste bin 提供:http://pastebin.com/nuqdeY8z

以下是归属:

/* Copyright (C) 2007  The Trustees of Indiana University
 *
 * Use, modification and distribution is subject to the Boost Software
 * License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
 * http://www.boost.org/LICENSE_1_0.txt)
 *  
 * Authors: Douglas Gregor
 *          Andrew Lumsdaine
 *          
 * Url:     http://www.osl.iu.edu/research/mpi.net/svn/
 *
 * This file provides the "Operations" class, which contains common
 * reduction operations such as addition and multiplication for any
 * type.
 *
 * This code was heavily influenced by Keith Farmer's
 *   Operator Overloading with Generics
 * at http://www.codeproject.com/csharp/genericoperators.asp
 *
 * All MPI related code removed by ja72. 
 */

哦,我只看到了 dynamic 关键字的解决方案。 - John Alexiou

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接