次のようなジェネリッククラス定義が与えられます
public class ConstrainedNumber<T> :
IEquatable<ConstrainedNumber<T>>,
IEquatable<T>,
IComparable<ConstrainedNumber<T>>,
IComparable<T>,
IComparable where T:struct, IComparable, IComparable<T>, IEquatable<T>
どうすれば算術演算子を定義できますか?
'+'演算子はタイプ 'T'および 'T'に適用できないため、以下はコンパイルされません。
public static T operator +( ConstrainedNumber<T> x, ConstrainedNumber<T> y)
{
return x._value + y._value;
}
ご覧のとおり、ジェネリック型「T」は「where」キーワードで制約されていますが、算術演算子(IArithmetic?)を持つ数値型には制約が必要です。
'T'は、int、floatなどのプリミティブな数値型になります。そのような型に 'where'制約はありますか?
IConvertible
を制約として使用し、次のようなことを行うのが最善の方法だと思います。
public static operator T +(T x, T y)
where T: IConvertible
{
var type = typeof(T);
if (type == typeof(String) ||
type == typeof(DateTime)) throw new ArgumentException(String.Format("The type {0} is not supported", type.FullName), "T");
try { return (T)(Object)(x.ToDouble(NumberFormatInfo.CurrentInfo) + y.ToDouble(NumberFormatInfo.CurrentInfo)); }
catch(Exception ex) { throw new ApplicationException("The operation failed.", ex); }
}
ただし、誰かがStringまたはDateTimeを渡すのを防ぐことはできないので、手動でチェックすることをお勧めします。ただし、IConvertibleを使用すると、十分に近づき、操作を実行できるようになります。
残念ながら、ジェネリックパラメーターを整数型に制約する方法はありません(編集:「算術型」の方が良いWordかもしれません。整数だけに関係するものではありません)。
このようなことができるといいですね:
where T : integral // or "arithmetical" depending on how pedantic you are
または
where T : IArithmetic
Generic Operators 私たち自身のMarcGravellとJonSkeetによるものを読むことをお勧めします。これがなぜこれほど難しい問題であるのか、そしてそれを回避するために何ができるのかを説明します。
.NET 2.0はジェネリックスを.NETの世界に導入し、既存の問題に対する多くの洗練されたソリューションへの扉を開きました。ジェネリック制約を使用して、型引数を既知のインターフェイスなどに制限し、機能へのアクセスを確保することができます。または、単純な等式/不等式テストの場合、Comparer.DefaultおよびEqualityComparer.DefaultシングルトンはそれぞれIComparerおよびIEqualityComparerを実装します(たとえば、問題の「T」について何も知る必要はありません)。
とはいえ、オペレーターに関してはまだ大きなギャップがあります。演算子は静的メソッドとして宣言されているため、すべての数値型が実装するIMathまたは同様の同等のインターフェイスはありません。実際、オペレーターの柔軟性により、これを意味のある方法で行うのは非常に困難になります。さらに悪いことに:プリミティブ型の演算子の多くは演算子としても存在しません。代わりに、直接的なILメソッドがあります。[私の強調]状況をさらに複雑にするために、Nullable <>は「リフト演算子」の概念を要求します。ここで、内側の「T」は適用可能な演算子を表します。 null許容型に-しかし、これは言語機能として実装されており、ランタイムによって提供されません(リフレクションをさらに楽しくします)。
C#4.0では、 dynamic を使用してこの制限を回避できます。私はあなたのコードを見て、なんとか機能する(カットダウンではありますが)バージョンを作成しました:
public class ConstrainedNumber<T> where T : struct, IComparable, IComparable<T>, IEquatable<T>
{
private T _value;
public ConstrainedNumber(T value)
{
_value = value;
}
public static T operator +(ConstrainedNumber<T> x, ConstrainedNumber<T> y)
{
return (dynamic)x._value + y._value;
}
}
そしてそれに伴う小さなテストプログラム:
class Program
{
static void Main(string[] args)
{
ConstrainedNumber<int> one = new ConstrainedNumber<int>(10);
ConstrainedNumber<int> two = new ConstrainedNumber<int>(5);
var three = one + two;
Debug.Assert(three == 15);
Console.ReadLine();
}
}
楽しい!
いいえ、これは機能しません。しかし、問題を解決する方法についていくつかの提案があります。私は次のことをしました(ネット上のさまざまなソースからのいくつかのアイデアを使用して):
public delegate TResult BinaryOperator<TLeft, TRight, TResult>(TLeft left, TRight right);
/// <summary>
/// Provide efficient generic access to either native or static operators for the given type combination.
/// </summary>
/// <typeparam name="TLeft">The type of the left operand.</typeparam>
/// <typeparam name="TRight">The type of the right operand.</typeparam>
/// <typeparam name="TResult">The type of the result value.</typeparam>
/// <remarks>Inspired by Keith Farmer's code on CodeProject:<br/>http://www.codeproject.com/KB/cs/genericoperators.aspx</remarks>
public static class Operator<TLeft, TRight, TResult> {
private static BinaryOperator<TLeft, TRight, TResult> addition;
private static BinaryOperator<TLeft, TRight, TResult> bitwiseAnd;
private static BinaryOperator<TLeft, TRight, TResult> bitwiseOr;
private static BinaryOperator<TLeft, TRight, TResult> division;
private static BinaryOperator<TLeft, TRight, TResult> exclusiveOr;
private static BinaryOperator<TLeft, TRight, TResult> leftShift;
private static BinaryOperator<TLeft, TRight, TResult> modulus;
private static BinaryOperator<TLeft, TRight, TResult> multiply;
private static BinaryOperator<TLeft, TRight, TResult> rightShift;
private static BinaryOperator<TLeft, TRight, TResult> subtraction;
/// <summary>
/// Gets the addition operator + (either native or "op_Addition").
/// </summary>
/// <value>The addition operator.</value>
public static BinaryOperator<TLeft, TRight, TResult> Addition {
get {
if (addition == null) {
addition = CreateOperator("op_Addition", OpCodes.Add);
}
return addition;
}
}
/// <summary>
/// Gets the modulus operator % (either native or "op_Modulus").
/// </summary>
/// <value>The modulus operator.</value>
public static BinaryOperator<TLeft, TRight, TResult> Modulus {
get {
if (modulus == null) {
modulus = CreateOperator("op_Modulus", OpCodes.Rem);
}
return modulus;
}
}
/// <summary>
/// Gets the exclusive or operator ^ (either native or "op_ExclusiveOr").
/// </summary>
/// <value>The exclusive or operator.</value>
public static BinaryOperator<TLeft, TRight, TResult> ExclusiveOr {
get {
if (exclusiveOr == null) {
exclusiveOr = CreateOperator("op_ExclusiveOr", OpCodes.Xor);
}
return exclusiveOr;
}
}
/// <summary>
/// Gets the bitwise and operator & (either native or "op_BitwiseAnd").
/// </summary>
/// <value>The bitwise and operator.</value>
public static BinaryOperator<TLeft, TRight, TResult> BitwiseAnd {
get {
if (bitwiseAnd == null) {
bitwiseAnd = CreateOperator("op_BitwiseAnd", OpCodes.And);
}
return bitwiseAnd;
}
}
/// <summary>
/// Gets the division operator / (either native or "op_Division").
/// </summary>
/// <value>The division operator.</value>
public static BinaryOperator<TLeft, TRight, TResult> Division {
get {
if (division == null) {
division = CreateOperator("op_Division", OpCodes.Div);
}
return division;
}
}
/// <summary>
/// Gets the multiplication operator * (either native or "op_Multiply").
/// </summary>
/// <value>The multiplication operator.</value>
public static BinaryOperator<TLeft, TRight, TResult> Multiply {
get {
if (multiply == null) {
multiply = CreateOperator("op_Multiply", OpCodes.Mul);
}
return multiply;
}
}
/// <summary>
/// Gets the bitwise or operator | (either native or "op_BitwiseOr").
/// </summary>
/// <value>The bitwise or operator.</value>
public static BinaryOperator<TLeft, TRight, TResult> BitwiseOr {
get {
if (bitwiseOr == null) {
bitwiseOr = CreateOperator("op_BitwiseOr", OpCodes.Or);
}
return bitwiseOr;
}
}
/// <summary>
/// Gets the left shift operator << (either native or "op_LeftShift").
/// </summary>
/// <value>The left shift operator.</value>
public static BinaryOperator<TLeft, TRight, TResult> LeftShift {
get {
if (leftShift == null) {
leftShift = CreateOperator("op_LeftShift", OpCodes.Shl);
}
return leftShift;
}
}
/// <summary>
/// Gets the right shift operator >> (either native or "op_RightShift").
/// </summary>
/// <value>The right shift operator.</value>
public static BinaryOperator<TLeft, TRight, TResult> RightShift {
get {
if (rightShift == null) {
rightShift = CreateOperator("op_RightShift", OpCodes.Shr);
}
return rightShift;
}
}
/// <summary>
/// Gets the subtraction operator - (either native or "op_Addition").
/// </summary>
/// <value>The subtraction operator.</value>
public static BinaryOperator<TLeft, TRight, TResult> Subtraction {
get {
if (subtraction == null) {
subtraction = CreateOperator("op_Subtraction", OpCodes.Sub);
}
return subtraction;
}
}
private static BinaryOperator<TLeft, TRight, TResult> CreateOperator(string operatorName, OpCode opCode) {
if (operatorName == null) {
throw new ArgumentNullException("operatorName");
}
bool isPrimitive = true;
bool isLeftNullable;
bool isRightNullable = false;
Type leftType = typeof(TLeft);
Type rightType = typeof(TRight);
MethodInfo operatorMethod = LookupOperatorMethod(ref leftType, operatorName, ref isPrimitive, out isLeftNullable) ??
LookupOperatorMethod(ref rightType, operatorName, ref isPrimitive, out isRightNullable);
DynamicMethod method = new DynamicMethod(string.Format("{0}:{1}:{2}:{3}", operatorName, typeof(TLeft).FullName, typeof(TRight).FullName, typeof(TResult).FullName), typeof(TResult),
new Type[] {typeof(TLeft), typeof(TRight)});
Debug.WriteLine(method.Name, "Generating operator method");
ILGenerator generator = method.GetILGenerator();
if (isPrimitive) {
Debug.WriteLine("Primitives using opcode", "Emitting operator code");
generator.Emit(OpCodes.Ldarg_0);
if (isLeftNullable) {
generator.EmitCall(OpCodes.Call, typeof(TLeft).GetMethod("op_Explicit", BindingFlags.Public|BindingFlags.Static), null);
}
IlTypeHelper.ILType stackType = IlTypeHelper.EmitWidening(generator, IlTypeHelper.GetILType(leftType), IlTypeHelper.GetILType(rightType));
generator.Emit(OpCodes.Ldarg_1);
if (isRightNullable) {
generator.EmitCall(OpCodes.Call, typeof(TRight).GetMethod("op_Explicit", BindingFlags.Public | BindingFlags.Static), null);
}
stackType = IlTypeHelper.EmitWidening(generator, IlTypeHelper.GetILType(rightType), stackType);
generator.Emit(opCode);
if (typeof(TResult) == typeof(object)) {
generator.Emit(OpCodes.Box, IlTypeHelper.GetPrimitiveType(stackType));
} else {
Type resultType = typeof(TResult);
if (IsNullable(ref resultType)) {
generator.Emit(OpCodes.Newobj, typeof(TResult).GetConstructor(new Type[] {resultType}));
} else {
IlTypeHelper.EmitExplicit(generator, stackType, IlTypeHelper.GetILType(resultType));
}
}
} else if (operatorMethod != null) {
Debug.WriteLine("Call to static operator method", "Emitting operator code");
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldarg_1);
generator.EmitCall(OpCodes.Call, operatorMethod, null);
if (typeof(TResult).IsPrimitive && operatorMethod.ReturnType.IsPrimitive) {
IlTypeHelper.EmitExplicit(generator, IlTypeHelper.GetILType(operatorMethod.ReturnType), IlTypeHelper.GetILType(typeof(TResult)));
} else if (!typeof(TResult).IsAssignableFrom(operatorMethod.ReturnType)) {
Debug.WriteLine("Conversion to return type", "Emitting operator code");
generator.Emit(OpCodes.Ldtoken, typeof(TResult));
generator.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle", new Type[] {typeof(RuntimeTypeHandle)}), null);
generator.EmitCall(OpCodes.Call, typeof(Convert).GetMethod("ChangeType", new Type[] {typeof(object), typeof(Type)}), null);
}
} else {
Debug.WriteLine("Throw NotSupportedException", "Emitting operator code");
generator.ThrowException(typeof(NotSupportedException));
}
generator.Emit(OpCodes.Ret);
return (BinaryOperator<TLeft, TRight, TResult>)method.CreateDelegate(typeof(BinaryOperator<TLeft, TRight, TResult>));
}
private static bool IsNullable(ref Type type) {
if (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(Nullable<>))) {
type = type.GetGenericArguments()[0];
return true;
}
return false;
}
private static MethodInfo LookupOperatorMethod(ref Type type, string operatorName, ref bool isPrimitive, out bool isNullable) {
isNullable = IsNullable(ref type);
if (!type.IsPrimitive) {
isPrimitive = false;
foreach (MethodInfo methodInfo in type.GetMethods(BindingFlags.Static|BindingFlags.Public)) {
if (methodInfo.Name == operatorName) {
bool isMatch = true;
foreach (ParameterInfo parameterInfo in methodInfo.GetParameters()) {
switch (parameterInfo.Position) {
case 0:
if (parameterInfo.ParameterType != typeof(TLeft)) {
isMatch = false;
}
break;
case 1:
if (parameterInfo.ParameterType != typeof(TRight)) {
isMatch = false;
}
break;
default:
isMatch = false;
break;
}
}
if (isMatch) {
if (typeof(TResult).IsAssignableFrom(methodInfo.ReturnType) || typeof(IConvertible).IsAssignableFrom(methodInfo.ReturnType)) {
return methodInfo; // full signature match
}
}
}
}
}
return null;
}
}
internal static class IlTypeHelper {
[Flags]
public enum ILType {
None = 0,
Unsigned = 1,
B8 = 2,
B16 = 4,
B32 = 8,
B64 = 16,
Real = 32,
I1 = B8, // 2
U1 = B8|Unsigned, // 3
I2 = B16, // 4
U2 = B16|Unsigned, // 5
I4 = B32, // 8
U4 = B32|Unsigned, // 9
I8 = B64, //16
U8 = B64|Unsigned, //17
R4 = B32|Real, //40
R8 = B64|Real //48
}
public static ILType GetILType(Type type) {
if (type == null) {
throw new ArgumentNullException("type");
}
if (!type.IsPrimitive) {
throw new ArgumentException("IL native operations requires primitive types", "type");
}
if (type == typeof(double)) {
return ILType.R8;
}
if (type == typeof(float)) {
return ILType.R4;
}
if (type == typeof(ulong)) {
return ILType.U8;
}
if (type == typeof(long)) {
return ILType.I8;
}
if (type == typeof(uint)) {
return ILType.U4;
}
if (type == typeof(int)) {
return ILType.I4;
}
if (type == typeof(short)) {
return ILType.U2;
}
if (type == typeof(ushort)) {
return ILType.I2;
}
if (type == typeof(byte)) {
return ILType.U1;
}
if (type == typeof(sbyte)) {
return ILType.I1;
}
return ILType.None;
}
public static Type GetPrimitiveType(ILType iLType) {
switch (iLType) {
case ILType.R8:
return typeof(double);
case ILType.R4:
return typeof(float);
case ILType.U8:
return typeof(ulong);
case ILType.I8:
return typeof(long);
case ILType.U4:
return typeof(uint);
case ILType.I4:
return typeof(int);
case ILType.U2:
return typeof(short);
case ILType.I2:
return typeof(ushort);
case ILType.U1:
return typeof(byte);
case ILType.I1:
return typeof(sbyte);
}
throw new ArgumentOutOfRangeException("iLType");
}
public static ILType EmitWidening(ILGenerator generator, ILType onStackIL, ILType otherIL) {
if (generator == null) {
throw new ArgumentNullException("generator");
}
if (onStackIL == ILType.None) {
throw new ArgumentException("Stack needs a value", "onStackIL");
}
if (onStackIL < ILType.I8) {
onStackIL = ILType.I8;
}
if ((onStackIL < otherIL) && (onStackIL != ILType.R4)) {
switch (otherIL) {
case ILType.R4:
case ILType.R8:
if ((onStackIL&ILType.Unsigned) == ILType.Unsigned) {
generator.Emit(OpCodes.Conv_R_Un);
} else if (onStackIL != ILType.R4) {
generator.Emit(OpCodes.Conv_R8);
} else {
return ILType.R4;
}
return ILType.R8;
case ILType.U8:
case ILType.I8:
if ((onStackIL&ILType.Unsigned) == ILType.Unsigned) {
generator.Emit(OpCodes.Conv_U8);
return ILType.U8;
}
if (onStackIL != ILType.I8) {
generator.Emit(OpCodes.Conv_I8);
}
return ILType.I8;
}
}
return onStackIL;
}
public static void EmitExplicit(ILGenerator generator, ILType onStackIL, ILType otherIL) {
if (otherIL != onStackIL) {
switch (otherIL) {
case ILType.I1:
generator.Emit(OpCodes.Conv_I1);
break;
case ILType.I2:
generator.Emit(OpCodes.Conv_I2);
break;
case ILType.I4:
generator.Emit(OpCodes.Conv_I4);
break;
case ILType.I8:
generator.Emit(OpCodes.Conv_I8);
break;
case ILType.U1:
generator.Emit(OpCodes.Conv_U1);
break;
case ILType.U2:
generator.Emit(OpCodes.Conv_U2);
break;
case ILType.U4:
generator.Emit(OpCodes.Conv_U4);
break;
case ILType.U8:
generator.Emit(OpCodes.Conv_U8);
break;
case ILType.R4:
generator.Emit(OpCodes.Conv_R4);
break;
case ILType.R8:
generator.Emit(OpCodes.Conv_R8);
break;
}
}
}
}
次のように使用します。inti= Operator.Addition(3、5);
そのために利用できる制約はありませんが、問題を回避する方法があります。
public static T operator -(T foo, T bar)
{
return (T)System.Convert.ChangeType(
System.Convert.ToDecimal(foo)
-
System.Convert.ToDecimal(bar),
typeof(T));
}
ジェネリック引数として使用される多くの型を使用しておらず、コンパイル時のチェックが必要な場合は、Luceroのソリューションと同様のソリューションを使用できます。
public class Arithmetic<T>
{
protected static readonly Func<T, T, T> OP_ADD;
protected static readonly Func<T, T, T> OP_MUL;
protected static readonly Func<T, T, T> OP_SUB;
/* Define all operators you need here */
static Arithmetic()
{
Arithmetic<Single>.OP_ADD = (x, y) => x + y;
Arithmetic<Single>.OP_MUL = (x, y) => x * y;
Arithmetic<Single>.OP_SUB = (x, y) => x - y;
Arithmetic<Double>.OP_ADD = (x, y) => x + y;
Arithmetic<Double>.OP_MUL = (x, y) => x * y;
Arithmetic<Double>.OP_SUB = (x, y) => x - y;
/* This could also be generated by a tool */
}
}
public class Vector2<T> : Arithmetic<T>
{
public T X;
public T Y;
public static Vector2<T> operator +(Vector2<T> a, Vector2<T> b)
{
return new Vector2<T>()
{
X = OP_ADD(a.X, b.X),
Y = OP_ADD(a.Y, b.Y)
};
}
public static Vector2<T> operator -(Vector2<T> a, Vector2<T> b)
{
return new Vector2<T>()
{
X = OP_SUB(a.X, b.X),
Y = OP_SUB(a.Y, b.Y)
};
}
public static Vector2<T> operator *(Vector2<T> a, Vector2<T> b)
{
return new Vector2<T>()
{
X = OP_MUL(a.X, b.X),
Y = OP_MUL(a.Y, b.Y)
};
}
}
この友達はどうですか(RTTIとオブジェクトクラスを使用)
class MyMath
{
public static T Add<T>(T a, T b) where T: struct
{
switch (typeof(T).Name)
{
case "Int32":
return (T) (object)((int)(object)a + (int)(object)b);
case "Double":
return (T)(object)((double)(object)a + (double)(object)b);
default:
return default(T);
}
}
}
class Program
{
public static int Main()
{
Console.WriteLine(MyMath.Add<double>(3.6, 2.12));
return 0;
}
}
私はここを見た後にこれをしました。 Vector4 <T>クラスには、通常のベクトル演算を使用したタイプTの4つの数値/軸が含まれています。 Decimalとの間で変換する2つの暗黙的な操作を追加するだけです。これはおそらくあなたが得ようとしているのと同じくらい非言語的ですが、あなたが指摘するように、より正確であり、したがって必要以上に重いです。あなたたちのように、私はINumericか何かがあったらいいのにと思います!
public static Vector4<T> operator +(Vector4<T> a, Vector4<T> b)
{
Vector4<Decimal> A = a;
Vector4<Decimal> B = b;
var result = new Vector4<Decimal>(A.X + B.X, A.Y + B.Y, A.Z + B.Z, A.W + B.W);
return result;
}
public static implicit operator Vector4<Decimal>(Vector4<T> v)
{
return new Vector4<Decimal>(
Convert.ToDecimal(v.X),
Convert.ToDecimal(v.Y),
Convert.ToDecimal(v.Z),
Convert.ToDecimal(v.W));
}
public static implicit operator Vector4<T>(Vector4<Decimal> v)
{
return new Vector4<T>(
(T)Convert.ChangeType(v.X, typeof(T)),
(T)Convert.ChangeType(v.Y, typeof(T)),
(T)Convert.ChangeType(v.Z, typeof(T)),
(T)Convert.ChangeType(v.W, typeof(T)));
}
.Netジェネリックには、演算子がサポートされていることを示す現在のサポートはありません。
これはよくリクエストされる機能です。
それは半回避することができます( MiscUtils を参照)が、これはあなたが望む構文を与えません
私がこのようなことをしなければならなかったら、私はおそらくそれに沿ってそれに近づくでしょう
public class ConstrainedNumber<T>
{
private T Value { get; }
public ConstrainedNumber(T value)
{
Value = value;
}
private static Func<ConstrainedNumber<T>, ConstrainedNumber<T>, T> _addFunc; // Cache the delegate
public static ConstrainedNumber<T> operator+(ConstrainedNumber<T> left, ConstrainedNumber<T> right)
{
var adder = _addFunc;
if (adder == null)
{
ParameterExpression lhs = Expression.Parameter(typeof(ConstrainedNumber<T>));
ParameterExpression rhs = Expression.Parameter(typeof(ConstrainedNumber<T>));
_addFunc = adder = Expression.Lambda<Func<ConstrainedNumber<T>, ConstrainedNumber<T>, T>>(
Expression.Add(
Expression.Property(lhs, nameof(Value)),
Expression.Property(lhs, nameof(Value))
),
lhs,
rhs).Compile();
}
return new ConstrainedNumber<T>(adder(left, right));
}
}
最終結果は、dynamic
アプローチの最終結果に少し似ています。これは、実際には内部でこのようなことを行うことになりますが、オーバーヘッドが少し多くなり、どのT
でも機能するはずです。算術プリミティブであるか、+
演算子が定義されています。 dynamic
アプローチの処理が異なる1つのケースは、string
に対しては機能するが、機能しない場合です。それが良いか悪いかはユースケースによって異なりますが、必要な場合はstring
を特殊なケースにすることができます。
演算子式が手動で作成される式ツリーを含むいくつかの潜在的な解決策を見てきました。
コンパイル時の検証が失われるため、完全ではありませんが、うまくいく可能性があります。
ここにあります それについての記事。
残念ながら、整数用に定義されたIArithmetic
(あなたが言ったように)インターフェースがないため、これは不可能です。これらのプリミティブ型は、そのようなインターフェイスを実装するクラスでラップできます。