Is there a constraint that restricts my generic method to numeric types?

后端 未结 21 2604
栀梦
栀梦 2020-11-21 05:48

Can anyone tell me if there is a way with generics to limit a generic type argument T to only:

  • Int16
  • Int32
相关标签:
21条回答
  • 2020-11-21 06:17

    This question is a bit of a FAQ one, so I'm posting this as wiki (since I've posted similar before, but this is an older one); anyway...

    What version of .NET are you using? If you are using .NET 3.5, then I have a generic operators implementation in MiscUtil (free etc).

    This has methods like T Add<T>(T x, T y), and other variants for arithmetic on different types (like DateTime + TimeSpan).

    Additionally, this works for all the inbuilt, lifted and bespoke operators, and caches the delegate for performance.

    Some additional background on why this is tricky is here.

    You may also want to know that dynamic (4.0) sort-of solves this issue indirectly too - i.e.

    dynamic x = ..., y = ...
    dynamic result = x + y; // does what you expect
    
    0 讨论(0)
  • 2020-11-21 06:17

    Unfortunately you are only able to specify struct in the where clause in this instance. It does seem strange you can't specify Int16, Int32, etc. specifically but I'm sure there's some deep implementation reason underlying the decision to not permit value types in a where clause.

    I guess the only solution is to do a runtime check which unfortunately prevents the problem being picked up at compile time. That'd go something like:-

    static bool IntegerFunction<T>(T value) where T : struct {
      if (typeof(T) != typeof(Int16)  &&
          typeof(T) != typeof(Int32)  &&
          typeof(T) != typeof(Int64)  &&
          typeof(T) != typeof(UInt16) &&
          typeof(T) != typeof(UInt32) &&
          typeof(T) != typeof(UInt64)) {
        throw new ArgumentException(
          string.Format("Type '{0}' is not valid.", typeof(T).ToString()));
      }
    
      // Rest of code...
    }
    

    Which is a little bit ugly I know, but at least provides the required constraints.

    I'd also look into possible performance implications for this implementation, perhaps there's a faster way out there.

    0 讨论(0)
  • 2020-11-21 06:19

    There is no way to restrict templates to types, but you can define different actions based on the type. As part of a generic numeric package, I needed a generic class to add two values.

        class Something<TCell>
        {
            internal static TCell Sum(TCell first, TCell second)
            {
                if (typeof(TCell) == typeof(int))
                    return (TCell)((object)(((int)((object)first)) + ((int)((object)second))));
    
                if (typeof(TCell) == typeof(double))
                    return (TCell)((object)(((double)((object)first)) + ((double)((object)second))));
    
                return second;
            }
        }
    

    Note that the typeofs are evaluated at compile time, so the if statements would be removed by the compiler. The compiler also removes spurious casts. So Something would resolve in the compiler to

            internal static int Sum(int first, int second)
            {
                return first + second;
            }
    
    0 讨论(0)
  • 2020-11-21 06:20

    Topic is old but for future readers:

    This feature is tightly related to Discriminated Unions which is not implemented in C# so far. I found its issue here:

    https://github.com/dotnet/csharplang/issues/113

    This issue is still open and feature has been planned for C# 10

    So still we have to wait a bit more, but after releasing you can do it this way:

    static bool IntegerFunction<T>(T value) where T : Int16 | Int32 | Int64 | ...
    
    0 讨论(0)
  • 2020-11-21 06:24

    Unfortunately .NET doesn't provide a way to do that natively.

    To address this issue I created the OSS library Genumerics which provides most standard numeric operations for the following built-in numeric types and their nullable equivalents with the ability to add support for other numeric types.

    sbyte, byte, short, ushort, int, uint, long, ulong, float, double, decimal, and BigInteger

    The performance is equivalent to a numeric type specific solution allowing you to create efficient generic numeric algorithms.

    Here's an example of the code usage.

    public static T Sum(T[] items)
    {
        T sum = Number.Zero<T>();
        foreach (T item in items)
        {
            sum = Number.Add(sum, item);
        }
        return sum;
    }
    
    public static T SumAlt(T[] items)
    {
        // implicit conversion to Number<T>
        Number<T> sum = Number.Zero<T>();
        foreach (T item in items)
        {
            // operator support
            sum += item;
        }
        // implicit conversion to T
        return sum;
    }
    
    0 讨论(0)
  • 2020-11-21 06:28

    C# does not support this. Hejlsberg has described the reasons for not implementing the feature in an interview with Bruce Eckel:

    And it's not clear that the added complexity is worth the small yield that you get. If something you want to do is not directly supported in the constraint system, you can do it with a factory pattern. You could have a Matrix<T>, for example, and in that Matrix you would like to define a dot product method. That of course that means you ultimately need to understand how to multiply two Ts, but you can't say that as a constraint, at least not if T is int, double, or float. But what you could do is have your Matrix take as an argument a Calculator<T>, and in Calculator<T>, have a method called multiply. You go implement that and you pass it to the Matrix.

    However, this leads to fairly convoluted code, where the user has to supply their own Calculator<T> implementation, for each T that they want to use. As long as it doesn’t have to be extensible, i.e. if you just want to support a fixed number of types, such as int and double, you can get away with a relatively simple interface:

    var mat = new Matrix<int>(w, h);
    

    (Minimal implementation in a GitHub Gist.)

    However, as soon as you want the user to be able to supply their own, custom types, you need to open up this implementation so that the user can supply their own Calculator instances. For instance, to instantiate a matrix that uses a custom decimal floating point implementation, DFP, you’d have to write this code:

    var mat = new Matrix<DFP>(DfpCalculator.Instance, w, h);
    

    … and implement all the members for DfpCalculator : ICalculator<DFP>.

    An alternative, which unfortunately shares the same limitations, is to work with policy classes, as discussed in Sergey Shandar’s answer.

    0 讨论(0)
提交回复
热议问题