Решение для перегруженного ограничения оператора в .NET-дженериках
Что мне делать, если я хочу иметь общий метод, который принимает только типы, которые перегружали оператор, например, оператор вычитания. Я попытался использовать интерфейс в качестве ограничения, но интерфейсы не могут иметь перегрузку оператора.
Каков наилучший способ достичь этого?
Ответы
Ответ 1
Нет немедленного ответа; операторы являются статическими и не могут быть выражены в ограничениях - и существующие примитивы не реализуют какой-либо конкретный интерфейс (контраст с IComparable [<T> ], который может использоваться для эмуляции большего или меньшего).
Тем не менее; если вы просто хотите, чтобы он работал, то в .NET 3.5 есть несколько вариантов...
Я собрал здесь библиотеку которая позволяет эффективный и простой доступ к операторам с дженериками - например:
T result = Operator.Add(first, second); // implicit <T>; here
Его можно загрузить как часть MiscUtil
Кроме того, в С# 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>;
через все методы, которые становятся беспорядочными.
Ответ 2
Я обнаружил, что IL действительно может справиться с этим достаточно хорошо. Ex.
ldarg.0
ldarg.1
add
ret
Скомпилированный в общем методе, код будет работать нормально до тех пор, пока не будет указан примитивный тип. Возможно, это можно расширить, чтобы вызвать операторские функции для не-примитивных типов.
Смотрите здесь.
Ответ 3
Есть фрагмент кода, украденный у интернатов, который я использую для этого много. Он ищет или строит с использованием основных арифметических операторов IL
. Все это выполняется в классе Operation<T>
generic, и все, что вам нужно сделать, - назначить требуемую операцию в делегат. Как 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>
Исходный код предоставлен пастой 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.
*/