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

后端 未结 21 2593
栀梦
栀梦 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:11

    If you are using .NET 4.0 and later then you can just use dynamic as method argument and check in runtime that the passed dynamic argument type is numeric/integer type.

    If the type of the passed dynamic is not numeric/integer type then throw exception.

    An example short code that implements the idea is something like:

    using System;
    public class InvalidArgumentException : Exception
    {
        public InvalidArgumentException(string message) : base(message) {}
    }
    public class InvalidArgumentTypeException : InvalidArgumentException
    {
        public InvalidArgumentTypeException(string message) : base(message) {}
    }
    public class ArgumentTypeNotIntegerException : InvalidArgumentTypeException
    {
        public ArgumentTypeNotIntegerException(string message) : base(message) {}
    }
    public static class Program
    {
        private static bool IntegerFunction(dynamic n)
        {
            if (n.GetType() != typeof(Int16) &&
                n.GetType() != typeof(Int32) &&
                n.GetType() != typeof(Int64) &&
                n.GetType() != typeof(UInt16) &&
                n.GetType() != typeof(UInt32) &&
                n.GetType() != typeof(UInt64))
                throw new ArgumentTypeNotIntegerException("argument type is not integer type");
            //code that implements IntegerFunction goes here
        }
        private static void Main()
        {
             Console.WriteLine("{0}",IntegerFunction(0)); //Compiles, no run time error and first line of output buffer is either "True" or "False" depends on the code that implements "Program.IntegerFunction" static method.
             Console.WriteLine("{0}",IntegerFunction("string")); //Also compiles but it is run time error and exception of type "ArgumentTypeNotIntegerException" is thrown here.
             Console.WriteLine("This is the last Console.WriteLine output"); //Never reached and executed due the run time error and the exception thrown on the second line of Program.Main static method.
        }
    

    Of course that this solution works in run time only but never in compile time.

    If you want a solution that always works in compile time and never in run time then you will have to wrap the dynamic with a public struct/class whose overloaded public constructors accept arguments of the desired types only and give the struct/class appropriate name.

    It makes sense that the wrapped dynamic is always private member of the class/struct and it is the only member of the struct/class and the name of the only member of the struct/class is "value".

    You will also have to define and implement public methods and/or operators that work with the desired types for the private dynamic member of the class/struct if necessary.

    It also makes sense that the struct/class has special/unique constructor that accepts dynamic as argument that initializes it's only private dynamic member called "value" but the modifier of this constructor is private of course.

    Once the class/struct is ready define the argument's type of IntegerFunction to be that class/struct that has been defined.

    An example long code that implements the idea is something like:

    using System;
    public struct Integer
    {
        private dynamic value;
        private Integer(dynamic n) { this.value = n; }
        public Integer(Int16 n) { this.value = n; }
        public Integer(Int32 n) { this.value = n; }
        public Integer(Int64 n) { this.value = n; }
        public Integer(UInt16 n) { this.value = n; }
        public Integer(UInt32 n) { this.value = n; }
        public Integer(UInt64 n) { this.value = n; }
        public Integer(Integer n) { this.value = n.value; }
        public static implicit operator Int16(Integer n) { return n.value; }
        public static implicit operator Int32(Integer n) { return n.value; }
        public static implicit operator Int64(Integer n) { return n.value; }
        public static implicit operator UInt16(Integer n) { return n.value; }
        public static implicit operator UInt32(Integer n) { return n.value; }
        public static implicit operator UInt64(Integer n) { return n.value; }
        public static Integer operator +(Integer x, Int16 y) { return new Integer(x.value + y); }
        public static Integer operator +(Integer x, Int32 y) { return new Integer(x.value + y); }
        public static Integer operator +(Integer x, Int64 y) { return new Integer(x.value + y); }
        public static Integer operator +(Integer x, UInt16 y) { return new Integer(x.value + y); }
        public static Integer operator +(Integer x, UInt32 y) { return new Integer(x.value + y); }
        public static Integer operator +(Integer x, UInt64 y) { return new Integer(x.value + y); }
        public static Integer operator -(Integer x, Int16 y) { return new Integer(x.value - y); }
        public static Integer operator -(Integer x, Int32 y) { return new Integer(x.value - y); }
        public static Integer operator -(Integer x, Int64 y) { return new Integer(x.value - y); }
        public static Integer operator -(Integer x, UInt16 y) { return new Integer(x.value - y); }
        public static Integer operator -(Integer x, UInt32 y) { return new Integer(x.value - y); }
        public static Integer operator -(Integer x, UInt64 y) { return new Integer(x.value - y); }
        public static Integer operator *(Integer x, Int16 y) { return new Integer(x.value * y); }
        public static Integer operator *(Integer x, Int32 y) { return new Integer(x.value * y); }
        public static Integer operator *(Integer x, Int64 y) { return new Integer(x.value * y); }
        public static Integer operator *(Integer x, UInt16 y) { return new Integer(x.value * y); }
        public static Integer operator *(Integer x, UInt32 y) { return new Integer(x.value * y); }
        public static Integer operator *(Integer x, UInt64 y) { return new Integer(x.value * y); }
        public static Integer operator /(Integer x, Int16 y) { return new Integer(x.value / y); }
        public static Integer operator /(Integer x, Int32 y) { return new Integer(x.value / y); }
        public static Integer operator /(Integer x, Int64 y) { return new Integer(x.value / y); }
        public static Integer operator /(Integer x, UInt16 y) { return new Integer(x.value / y); }
        public static Integer operator /(Integer x, UInt32 y) { return new Integer(x.value / y); }
        public static Integer operator /(Integer x, UInt64 y) { return new Integer(x.value / y); }
        public static Integer operator %(Integer x, Int16 y) { return new Integer(x.value % y); }
        public static Integer operator %(Integer x, Int32 y) { return new Integer(x.value % y); }
        public static Integer operator %(Integer x, Int64 y) { return new Integer(x.value % y); }
        public static Integer operator %(Integer x, UInt16 y) { return new Integer(x.value % y); }
        public static Integer operator %(Integer x, UInt32 y) { return new Integer(x.value % y); }
        public static Integer operator %(Integer x, UInt64 y) { return new Integer(x.value % y); }
        public static Integer operator +(Integer x, Integer y) { return new Integer(x.value + y.value); }
        public static Integer operator -(Integer x, Integer y) { return new Integer(x.value - y.value); }
        public static Integer operator *(Integer x, Integer y) { return new Integer(x.value * y.value); }
        public static Integer operator /(Integer x, Integer y) { return new Integer(x.value / y.value); }
        public static Integer operator %(Integer x, Integer y) { return new Integer(x.value % y.value); }
        public static bool operator ==(Integer x, Int16 y) { return x.value == y; }
        public static bool operator !=(Integer x, Int16 y) { return x.value != y; }
        public static bool operator ==(Integer x, Int32 y) { return x.value == y; }
        public static bool operator !=(Integer x, Int32 y) { return x.value != y; }
        public static bool operator ==(Integer x, Int64 y) { return x.value == y; }
        public static bool operator !=(Integer x, Int64 y) { return x.value != y; }
        public static bool operator ==(Integer x, UInt16 y) { return x.value == y; }
        public static bool operator !=(Integer x, UInt16 y) { return x.value != y; }
        public static bool operator ==(Integer x, UInt32 y) { return x.value == y; }
        public static bool operator !=(Integer x, UInt32 y) { return x.value != y; }
        public static bool operator ==(Integer x, UInt64 y) { return x.value == y; }
        public static bool operator !=(Integer x, UInt64 y) { return x.value != y; }
        public static bool operator ==(Integer x, Integer y) { return x.value == y.value; }
        public static bool operator !=(Integer x, Integer y) { return x.value != y.value; }
        public override bool Equals(object obj) { return this == (Integer)obj; }
        public override int GetHashCode() { return this.value.GetHashCode(); }
        public override string ToString() { return this.value.ToString(); }
        public static bool operator >(Integer x, Int16 y) { return x.value > y; }
        public static bool operator <(Integer x, Int16 y) { return x.value < y; }
        public static bool operator >(Integer x, Int32 y) { return x.value > y; }
        public static bool operator <(Integer x, Int32 y) { return x.value < y; }
        public static bool operator >(Integer x, Int64 y) { return x.value > y; }
        public static bool operator <(Integer x, Int64 y) { return x.value < y; }
        public static bool operator >(Integer x, UInt16 y) { return x.value > y; }
        public static bool operator <(Integer x, UInt16 y) { return x.value < y; }
        public static bool operator >(Integer x, UInt32 y) { return x.value > y; }
        public static bool operator <(Integer x, UInt32 y) { return x.value < y; }
        public static bool operator >(Integer x, UInt64 y) { return x.value > y; }
        public static bool operator <(Integer x, UInt64 y) { return x.value < y; }
        public static bool operator >(Integer x, Integer y) { return x.value > y.value; }
        public static bool operator <(Integer x, Integer y) { return x.value < y.value; }
        public static bool operator >=(Integer x, Int16 y) { return x.value >= y; }
        public static bool operator <=(Integer x, Int16 y) { return x.value <= y; }
        public static bool operator >=(Integer x, Int32 y) { return x.value >= y; }
        public static bool operator <=(Integer x, Int32 y) { return x.value <= y; }
        public static bool operator >=(Integer x, Int64 y) { return x.value >= y; }
        public static bool operator <=(Integer x, Int64 y) { return x.value <= y; }
        public static bool operator >=(Integer x, UInt16 y) { return x.value >= y; }
        public static bool operator <=(Integer x, UInt16 y) { return x.value <= y; }
        public static bool operator >=(Integer x, UInt32 y) { return x.value >= y; }
        public static bool operator <=(Integer x, UInt32 y) { return x.value <= y; }
        public static bool operator >=(Integer x, UInt64 y) { return x.value >= y; }
        public static bool operator <=(Integer x, UInt64 y) { return x.value <= y; }
        public static bool operator >=(Integer x, Integer y) { return x.value >= y.value; }
        public static bool operator <=(Integer x, Integer y) { return x.value <= y.value; }
        public static Integer operator +(Int16 x, Integer y) { return new Integer(x + y.value); }
        public static Integer operator +(Int32 x, Integer y) { return new Integer(x + y.value); }
        public static Integer operator +(Int64 x, Integer y) { return new Integer(x + y.value); }
        public static Integer operator +(UInt16 x, Integer y) { return new Integer(x + y.value); }
        public static Integer operator +(UInt32 x, Integer y) { return new Integer(x + y.value); }
        public static Integer operator +(UInt64 x, Integer y) { return new Integer(x + y.value); }
        public static Integer operator -(Int16 x, Integer y) { return new Integer(x - y.value); }
        public static Integer operator -(Int32 x, Integer y) { return new Integer(x - y.value); }
        public static Integer operator -(Int64 x, Integer y) { return new Integer(x - y.value); }
        public static Integer operator -(UInt16 x, Integer y) { return new Integer(x - y.value); }
        public static Integer operator -(UInt32 x, Integer y) { return new Integer(x - y.value); }
        public static Integer operator -(UInt64 x, Integer y) { return new Integer(x - y.value); }
        public static Integer operator *(Int16 x, Integer y) { return new Integer(x * y.value); }
        public static Integer operator *(Int32 x, Integer y) { return new Integer(x * y.value); }
        public static Integer operator *(Int64 x, Integer y) { return new Integer(x * y.value); }
        public static Integer operator *(UInt16 x, Integer y) { return new Integer(x * y.value); }
        public static Integer operator *(UInt32 x, Integer y) { return new Integer(x * y.value); }
        public static Integer operator *(UInt64 x, Integer y) { return new Integer(x * y.value); }
        public static Integer operator /(Int16 x, Integer y) { return new Integer(x / y.value); }
        public static Integer operator /(Int32 x, Integer y) { return new Integer(x / y.value); }
        public static Integer operator /(Int64 x, Integer y) { return new Integer(x / y.value); }
        public static Integer operator /(UInt16 x, Integer y) { return new Integer(x / y.value); }
        public static Integer operator /(UInt32 x, Integer y) { return new Integer(x / y.value); }
        public static Integer operator /(UInt64 x, Integer y) { return new Integer(x / y.value); }
        public static Integer operator %(Int16 x, Integer y) { return new Integer(x % y.value); }
        public static Integer operator %(Int32 x, Integer y) { return new Integer(x % y.value); }
        public static Integer operator %(Int64 x, Integer y) { return new Integer(x % y.value); }
        public static Integer operator %(UInt16 x, Integer y) { return new Integer(x % y.value); }
        public static Integer operator %(UInt32 x, Integer y) { return new Integer(x % y.value); }
        public static Integer operator %(UInt64 x, Integer y) { return new Integer(x % y.value); }
        public static bool operator ==(Int16 x, Integer y) { return x == y.value; }
        public static bool operator !=(Int16 x, Integer y) { return x != y.value; }
        public static bool operator ==(Int32 x, Integer y) { return x == y.value; }
        public static bool operator !=(Int32 x, Integer y) { return x != y.value; }
        public static bool operator ==(Int64 x, Integer y) { return x == y.value; }
        public static bool operator !=(Int64 x, Integer y) { return x != y.value; }
        public static bool operator ==(UInt16 x, Integer y) { return x == y.value; }
        public static bool operator !=(UInt16 x, Integer y) { return x != y.value; }
        public static bool operator ==(UInt32 x, Integer y) { return x == y.value; }
        public static bool operator !=(UInt32 x, Integer y) { return x != y.value; }
        public static bool operator ==(UInt64 x, Integer y) { return x == y.value; }
        public static bool operator !=(UInt64 x, Integer y) { return x != y.value; }
        public static bool operator >(Int16 x, Integer y) { return x > y.value; }
        public static bool operator <(Int16 x, Integer y) { return x < y.value; }
        public static bool operator >(Int32 x, Integer y) { return x > y.value; }
        public static bool operator <(Int32 x, Integer y) { return x < y.value; }
        public static bool operator >(Int64 x, Integer y) { return x > y.value; }
        public static bool operator <(Int64 x, Integer y) { return x < y.value; }
        public static bool operator >(UInt16 x, Integer y) { return x > y.value; }
        public static bool operator <(UInt16 x, Integer y) { return x < y.value; }
        public static bool operator >(UInt32 x, Integer y) { return x > y.value; }
        public static bool operator <(UInt32 x, Integer y) { return x < y.value; }
        public static bool operator >(UInt64 x, Integer y) { return x > y.value; }
        public static bool operator <(UInt64 x, Integer y) { return x < y.value; }
        public static bool operator >=(Int16 x, Integer y) { return x >= y.value; }
        public static bool operator <=(Int16 x, Integer y) { return x <= y.value; }
        public static bool operator >=(Int32 x, Integer y) { return x >= y.value; }
        public static bool operator <=(Int32 x, Integer y) { return x <= y.value; }
        public static bool operator >=(Int64 x, Integer y) { return x >= y.value; }
        public static bool operator <=(Int64 x, Integer y) { return x <= y.value; }
        public static bool operator >=(UInt16 x, Integer y) { return x >= y.value; }
        public static bool operator <=(UInt16 x, Integer y) { return x <= y.value; }
        public static bool operator >=(UInt32 x, Integer y) { return x >= y.value; }
        public static bool operator <=(UInt32 x, Integer y) { return x <= y.value; }
        public static bool operator >=(UInt64 x, Integer y) { return x >= y.value; }
        public static bool operator <=(UInt64 x, Integer y) { return x <= y.value; }
    }
    public static class Program
    {
        private static bool IntegerFunction(Integer n)
        {
            //code that implements IntegerFunction goes here
            //note that there is NO code that checks the type of n in rum time, because it is NOT needed anymore 
        }
        private static void Main()
        {
            Console.WriteLine("{0}",IntegerFunction(0)); //compile error: there is no overloaded METHOD for objects of type "int" and no implicit conversion from any object, including "int", to "Integer" is known.
            Console.WriteLine("{0}",IntegerFunction(new Integer(0))); //both compiles and no run time error
            Console.WriteLine("{0}",IntegerFunction("string")); //compile error: there is no overloaded METHOD for objects of type "string" and no implicit conversion from any object, including "string", to "Integer" is known.
            Console.WriteLine("{0}",IntegerFunction(new Integer("string"))); //compile error: there is no overloaded CONSTRUCTOR for objects of type "string"
        }
    }
    

    Note that in order to use dynamic in your code you must Add Reference to Microsoft.CSharp

    If the version of the .NET framework is below/under/lesser than 4.0 and dynamic is undefined in that version then you will have to use object instead and do casting to the integer type, which is trouble, so I recommend that you use at least .NET 4.0 or newer if you can so you can use dynamic instead of object.

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

    Beginning with C# 7.3, you can use closer approximation - the unmanaged constraint to specify that a type parameter is a non-pointer, non-nullable unmanaged type.

    class SomeGeneric<T> where T : unmanaged
    {
    //...
    }
    

    The unmanaged constraint implies the struct constraint and can't be combined with either the struct or new() constraints.

    A type is an unmanaged type if it's any of the following types:

    • sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, or bool
    • Any enum type
    • Any pointer type
    • Any user-defined struct type that contains fields of unmanaged types only and, in C# 7.3 and earlier, is not a constructed type (a type that includes at least one type argument)

    To restrict further and eliminate pointer and user-defined types that do not implement IComparable add IComparable (but enum is still derived from IComparable, so restrict enum by adding IEquatable < T >, you can go further depending on your circumstances and add additional interfaces. unmanaged allows to keep this list shorter):

        class SomeGeneric<T> where T : unmanaged, IComparable, IEquatable<T>
        {
        //...
        }
    

    But this doesn't prevent from DateTime instantiation.

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

    Considering the popularity of this question and the interest behind such a function I am surprised to see that there is no answer involving T4 yet.

    In this sample code I will demonstrate a very simple example of how you can use the powerful templating engine to do what the compiler pretty much does behind the scenes with generics.

    Instead of going through hoops and sacrificing compile-time certainty you can simply generate the function you want for every type you like and use that accordingly (at compile time!).

    In order to do this:

    • Create a new Text Template file called GenericNumberMethodTemplate.tt.
    • Remove the auto-generated code (you'll keep most of it, but some isn't needed).
    • Add the following snippet:
    <#@ template language="C#" #>
    <#@ output extension=".cs" #>
    <#@ assembly name="System.Core" #>
    
    <# Type[] types = new[] {
        typeof(Int16), typeof(Int32), typeof(Int64),
        typeof(UInt16), typeof(UInt32), typeof(UInt64)
        };
    #>
    
    using System;
    public static class MaxMath {
        <# foreach (var type in types) { 
        #>
            public static <#= type.Name #> Max (<#= type.Name #> val1, <#= type.Name #> val2) {
                return val1 > val2 ? val1 : val2;
            }
        <#
        } #>
    }
    

    That's it. You're done now.

    Saving this file will automatically compile it to this source file:

    using System;
    public static class MaxMath {
        public static Int16 Max (Int16 val1, Int16 val2) {
            return val1 > val2 ? val1 : val2;
        }
        public static Int32 Max (Int32 val1, Int32 val2) {
            return val1 > val2 ? val1 : val2;
        }
        public static Int64 Max (Int64 val1, Int64 val2) {
            return val1 > val2 ? val1 : val2;
        }
        public static UInt16 Max (UInt16 val1, UInt16 val2) {
            return val1 > val2 ? val1 : val2;
        }
        public static UInt32 Max (UInt32 val1, UInt32 val2) {
            return val1 > val2 ? val1 : val2;
        }
        public static UInt64 Max (UInt64 val1, UInt64 val2) {
            return val1 > val2 ? val1 : val2;
        }
    }
    

    In your main method you can verify that you have compile-time certainty:

    namespace TTTTTest
    {
        class Program
        {
            static void Main(string[] args)
            {
                long val1 = 5L;
                long val2 = 10L;
                Console.WriteLine(MaxMath.Max(val1, val2));
                Console.Read();
            }
        }
    }
    

    enter image description here

    I'll get ahead of one remark: no, this is not a violation of the DRY principle. The DRY principle is there to prevent people from duplicating code in multiple places that would cause the application to become hard to maintain.

    This is not at all the case here: if you want a change then you can just change the template (a single source for all your generation!) and it's done.

    In order to use it with your own custom definitions, add a namespace declaration (make sure it's the same one as the one where you'll define your own implementation) to your generated code and mark the class as partial. Afterwards, add these lines to your template file so it will be included in the eventual compilation:

    <#@ import namespace="TheNameSpaceYouWillUse" #>
    <#@ assembly name="$(TargetPath)" #>
    

    Let's be honest: This is pretty cool.

    Disclaimer: this sample has been heavily influenced by Metaprogramming in .NET by Kevin Hazzard and Jason Bock, Manning Publications.

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

    I created a little library functionality to solve these problems:

    Instead of:

    public T DifficultCalculation<T>(T a, T b)
    {
        T result = a * b + a; // <== WILL NOT COMPILE!
        return result;
    }
    Console.WriteLine(DifficultCalculation(2, 3)); // Should result in 8.
    

    You could write:

    public T DifficultCalculation<T>(Number<T> a, Number<T> b)
    {
        Number<T> result = a * b + a;
        return (T)result;
    }
    Console.WriteLine(DifficultCalculation(2, 3)); // Results in 8.
    

    You can find the source code here: https://codereview.stackexchange.com/questions/26022/improvement-requested-for-generic-calculator-and-generic-number

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

    Probably the closest you can do is

    static bool IntegerFunction<T>(T value) where T: struct
    

    Not sure if you could do the following

    static bool IntegerFunction<T>(T value) where T: struct, IComparable
    , IFormattable, IConvertible, IComparable<T>, IEquatable<T>
    

    For something so specific, why not just have overloads for each type, the list is so short and it would possibly have less memory footprint.

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

    There is no 'good' solution for this yet. However you can narrow the type argument significantly to rule out many missfits for your hypotetical 'INumeric' constraint as Haacked has shown above.

    static bool IntegerFunction<T>(T value) where T: IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>, struct {...

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