簡體   English   中英

是否存在將我的泛型方法限制為數字類型的約束?

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

誰能告訴我泛型是否有辦法將泛型類型參數T限制為:

  • Int16
  • Int32
  • Int64
  • UInt16
  • UInt32
  • UInt64

我知道where關鍵字,但找不到僅適用於這些類型的接口,

就像是:

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

C# 不支持這一點。 Hejlsberg在接受 Bruce Eckel 的采訪時描述了不實施該功能的原因:

而且還不清楚增加的復雜性是否值得您獲得的少量收益。 如果約束系統不直接支持你想做的事情,你可以用工廠模式來做。 例如,您可以有一個Matrix<T> ,並且在該Matrix中您想定義一個點積方法。 這當然意味着你最終需要了解如何將兩個T相乘,但你不能說這是一個約束,至少如果Tintdoublefloat則不是。 但是您可以做的是讓您的MatrixCalculator<T>作為參數,並在Calculator<T>中有一個名為multiply的方法。 你去實現它並將它傳遞給Matrix

但是,這會導致代碼相當復雜,用戶必須為他們想要使用的每個T提供他們自己的Calculator<T>實現。 只要它不必是可擴展的,即如果您只想支持固定數量的類型,例如intdouble ,您可以使用相對簡單的接口:

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

GitHub Gist 中的最小實現。

但是,只要您希望用戶能夠提供他們自己的自定義類型,您就需要打開這個實現,以便用戶可以提供他們自己的Calculator實例。 例如,要實例化使用自定義十進制浮點實現DFP的矩陣,您必須編寫以下代碼:

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

…並實現DfpCalculator : ICalculator<DFP>

正如 Sergey Shandar 的回答中所討論的,另一種方法(不幸的是具有相同的限制)是使用策略類。

考慮到這個問題的受歡迎程度以及這種功能背后的興趣,我驚訝地發現還沒有涉及 T4 的答案。

在這個示例代碼中,我將演示一個非常簡單的示例,說明如何使用強大的模板引擎來完成編譯器在后台使用泛型所做的工作。

您可以簡單地為您喜歡的每種類型生成所需的函數並相應地使用它(在編譯時!),而不是通過箍和犧牲編譯時確定性。

為此:

  • 創建一個名為GenericNumberMethodTemplate.tt的新文本模板文件。
  • 刪除自動生成的代碼(您將保留大部分代碼,但有些不需要)。
  • 添加以下代碼段:
<#@ 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;
        }
    <#
    } #>
}

而已。 你現在完成了。

保存這個文件會自動編譯成這個源文件:

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;
    }
}

在您的main方法中,您可以驗證您是否具有編譯時確定性:

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

在此處輸入圖像描述

我先說一句:不,這不違反 DRY 原則。 DRY 原則是為了防止人們在多個地方復制代碼,這會導致應用程序變得難以維護。

這里根本不是這種情況:如果您想要更改,那么您只需更改模板(您這一代人的單一來源!)就可以了。

為了將它與您自己的自定義定義一起使用,請在生成的代碼中添加一個命名空間聲明(確保它與您將定義自己的實現的命名空間聲明相同)並將該類標記為partial 然后,將這些行添加到您的模板文件中,以便將其包含在最終編譯中:

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

老實說:這很酷。

免責聲明:此示例深受Manning Publications 的 Kevin Hazzard 和 Jason Bock 的 .NET 元編程的影響。

對此沒有任何限制。 對於任何想要使用泛型進行數值計算的人來說,這都是一個真正的問題。

我會更進一步說我們需要

static bool GenericFunction<T>(T value) 
    where T : operators( +, -, /, * )

甚至

static bool GenericFunction<T>(T value) 
    where T : Add, Subtract

不幸的是,你只有接口、基類和關鍵字struct (必須是值類型)、 class (必須是引用類型)和new() (必須有默認構造函數)

您可以將數字包裝在其他東西(類似於INullable<T> )中,例如codeproject


您可以在運行時應用限制(通過反映運算符或檢查類型),但這確實失去了首先擁有泛型的優勢。

使用策略的解決方法:

interface INumericPolicy<T>
{
    T Zero();
    T Add(T a, T b);
    // add more functions here, such as multiplication etc.
}

struct NumericPolicies:
    INumericPolicy<int>,
    INumericPolicy<long>
    // add more INumericPolicy<> for different numeric types.
{
    int INumericPolicy<int>.Zero() { return 0; }
    long INumericPolicy<long>.Zero() { return 0; }
    int INumericPolicy<int>.Add(int a, int b) { return a + b; }
    long INumericPolicy<long>.Add(long a, long b) { return a + b; }
    // implement all functions from INumericPolicy<> interfaces.

    public static NumericPolicies Instance = new NumericPolicies();
}

算法:

static class Algorithms
{
    public static T Sum<P, T>(this P p, params T[] a)
        where P: INumericPolicy<T>
    {
        var r = p.Zero();
        foreach(var i in a)
        {
            r = p.Add(r, i);
        }
        return r;
    }

}

用法:

int i = NumericPolicies.Instance.Sum(1, 2, 3, 4, 5);
long l = NumericPolicies.Instance.Sum(1L, 2, 3, 4, 5);
NumericPolicies.Instance.Sum("www", "") // compile-time error.

該解決方案是編譯時安全的。 CityLizard Framework為 .NET 4.0 提供編譯版本。 該文件是 lib/NETFramework4.0/CityLizard.Policy.dll。

它也可以在 Nuget 中使用: https ://www.nuget.org/packages/CityLizard/。 請參閱CityLizard.Policy.I結構。

從 C# 7.3 開始,您可以使用更接近的近似值-非托管約束來指定類型參數是非指針、不可為空的非托管類型。

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

非托管約束意味着 struct 約束,不能與 struct 或 new() 約束結合使用。

如果類型是以下任何類型,則該類型是非托管類型:

  • sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal 或 bool
  • 任何枚舉類型
  • 任何指針類型
  • 任何僅包含非托管類型字段的用戶定義結構類型,並且在 C# 7.3 及更早版本中,都不是構造類型(包含至少一個類型參數的類型)

要進一步限制和消除不實現 IComparable 的指針和用戶定義類型添加IComparable (但枚舉仍然是從 IComparable 派生的,因此通過添加 IEquatable<T> 來限制枚舉,您可以根據自己的情況更進一步並添加額外的接口。 unmanaged 允許保持這個列表更短):

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

但這並不妨礙 DateTime 實例化。

這個問題有點像常見問題解答,所以我將其發布為 wiki(因為我之前發布過類似的問題,但這是一個較舊的問題); 反正...

您使用的是哪個版本的 .NET? 如果您使用的是 .NET 3.5,那么我在MiscUtil中有一個通用運算符實現(免費等)。

這具有T Add<T>(T x, T y)之類的方法,以及用於不同類型的算術運算的其他變體(例如DateTime + TimeSpan )。

此外,這適用於所有內置、提升和定制的運算符,並緩存委托以提高性能。

這里有一些關於為什么這很棘手的額外背景。

您可能還想知道dynamic (4.0)排序也間接解決了這個問題 - 即

dynamic x = ..., y = ...
dynamic result = x + y; // does what you expect

不幸的是,在這種情況下,您只能在 where 子句中指定 struct。 不能具體指定 Int16、Int32 等似乎很奇怪,但我確信在 where 子句中不允許值類型的決定背后有一些深層的實現原因。

我想唯一的解決方案是進行運行時檢查,不幸的是可以防止在編譯時發現問題。 那會是這樣的: -

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...
}

我知道這有點難看,但至少提供了所需的約束。

我還會研究此實現可能對性能產生的影響,也許有更快的方法。

可能你能做的最接近的是

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

不確定您是否可以執行以下操作

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

對於如此具體的事情,為什么不只是為每種類型都重載,列表是如此之短,它可能會占用更少的內存。

主題很舊,但對於未來的讀者:

此功能與目前尚未在 C# 中實現的可Discriminated Unions密切相關。 我在這里發現了它的問題:

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

此問題仍處於開放狀態,並且已為C# 10計划了功能

所以我們仍然需要再等一會兒,但是在發布之后你可以這樣做:

static bool IntegerFunction<T>(T value) where T : Int16 | Int32 | Int64 | ...

是的,或者說很快就會有!

查看此.NET 博客文章

從 .NET 6(我認為是預覽版 7)開始,您將能夠使用INumberIFloatingPoint等接口來創建以下程序:

using System;

Console.WriteLine(Sum(1, 2, 3, 4, 5));
Console.WriteLine(Sum(10.541, 2.645));
Console.WriteLine(Sum(1.55f, 5, 9.41f, 7));

static T Sum<T>(params T[] numbers) where T : INumber<T>
{
    T result = T.Zero;

    foreach (T item in numbers)
    {
        result += item;
    }

    return result;
}

INumber當前來自System.Runtime.Experimental NuGet 包。 我上面示例的項目文件看起來像

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <EnablePreviewFeatures>true</EnablePreviewFeatures>
        <OutputType>Exe</OutputType>
        <TargetFramework>net6.0</TargetFramework>
        <Nullable>enable</Nullable>
        <LangVersion>preview</LangVersion>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="System.Runtime.Experimental" Version="6.0.0-preview.7.21377.19" />
    </ItemGroup>

</Project>

還有IAdditionOperatorsIComparisonOperators等接口,因此您可以通用地使用特定的運算符。

無法將模板限制為類型,但您可以根據類型定義不同的操作。 作為通用數值包的一部分,我需要一個通用類來添加兩個值。

    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;
        }
    }

請注意,typeofs 是在編譯時評估的,因此編譯器將刪除 if 語句。 編譯器還刪除了虛假的強制轉換。 所以有些東西會在編譯器中解析為

        internal static int Sum(int first, int second)
        {
            return first + second;
        }

我創建了一個小庫功能來解決這些問題:

代替:

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.

你可以寫:

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.

你可以在這里找到源代碼: https ://codereview.stackexchange.com/questions/26022/improvement-requested-for-generic-calculator-and-generic-number

我有類似的情況,我需要處理數字類型和字符串; 看起來有點奇怪,但你去。

同樣,像許多人一樣,我查看了約束並提出了一堆它必須支持的接口。 但是,a) 它不是 100% 防水的,b) 任何新看這個長長的約束列表的人都會立即感到非常困惑。

所以,我的方法是將我所有的邏輯放入一個沒有約束的泛型方法中,但將該泛型方法設為私有。 然后我用公共方法公開它,一個明確處理我想要處理的類型 - 在我看來,代碼是干凈和明確的,例如

public static string DoSomething(this int input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this decimal input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this double input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this string input, ...) => DoSomethingHelper(input, ...);

private static string DoSomethingHelper<T>(this T input, ....)
{
    // complex logic
}

.NET 6 具有此功能的預覽功能:

https://devblogs.microsoft.com/dotnet/preview-features-in-net-6-generic-math/#generic-math

文章中的一個例子:

static T Add<T>(T left, T right)
    where T : INumber<T>
{
    return left + right;
}

INumber是一個實現其他接口的接口,例如允許泛型+使用的IAdditionOperators 這現在是可能的,因為另一個預覽特性是接口中的靜態抽象,因為+運算符重載是一個靜態方法:

/// <summary>Defines a mechanism for computing the sum of two values.</summary>
/// <typeparam name="TSelf">The type that implements this interface.</typeparam>
/// <typeparam name="TOther">The type that will be added to <typeparamref name="TSelf" />.</typeparam>
/// <typeparam name="TResult">The type that contains the sum of <typeparamref name="TSelf" /> and <typeparamref name="TOther" />.</typeparam>
[RequiresPreviewFeatures(Number.PreviewFeatureMessage, Url = Number.PreviewFeatureUrl)]
public interface IAdditionOperators<TSelf, TOther, TResult>
    where TSelf : IAdditionOperators<TSelf, TOther, TResult>
{
    /// <summary>Adds two values together to compute their sum.</summary>
    /// <param name="left">The value to which <paramref name="right" /> is added.</param>
    /// <param name="right">The value which is added to <paramref name="left" />.</param>
    /// <returns>The sum of <paramref name="left" /> and <paramref name="right" />.</returns>
    static abstract TResult operator +(TSelf left, TOther right);
}

我想知道和samjudson一樣,為什么只對整數? 如果是這種情況,您可能需要創建一個輔助類或類似的東西來保存您想要的所有類型。

如果您想要的只是整數,請不要使用泛型,那不是泛型; 或者更好的是,通過檢查其類型來拒絕任何其他類型。

如果您使用的是 .NET 4.0 及更高版本,那么您可以只使用動態作為方法參數並在運行時檢查傳遞的動態參數類型是數字/整數類型。

如果傳遞的動態類型不是數字/整數類型,則拋出異常。

實現該想法的示例代碼如下所示:

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.
    }

當然,該解決方案僅在運行時有效,但在編譯時無效。

如果您想要一個始終在編譯時工作而不是在運行時工作的解決方案,那么您必須使用公共結構/類來包裝動態,其重載的公共構造函數僅接受所需類型的參數並為結構/類提供適當的名稱。

有意義的是,包裝的動態始終是類/結構的私有成員,它是結構/類的唯一成員,結構/類的唯一成員的名稱是“值”。

如有必要,您還必須為類/結構的私有動態成員定義和實現與所需類型一起使用的公共方法和/或運算符。

同樣有意義的是,結構/類具有特殊/唯一構造函數,該構造函數接受動態作為參數,將其初始化為唯一稱為“值”的私有動態成員,但此構造函數的修飾符當然是私有的。

一旦類/結構准備就緒,將 IntegerFunction 的參數類型定義為已定義的類/結構。

實現該想法的示例代碼如下所示:

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"
    }
}

請注意,為了在您的代碼中使用動態,您必須添加對Microsoft.CSharp引用

如果 .NET 框架的版本低於/低於/小於 4.0 並且該版本中未定義動態,那么您將不得不使用object並強制轉換為整數類型,這很麻煩,所以我建議您使用 at如果可以的話,至少 .NET 4.0 或更高版本,這樣您就可以使用dynamic而不是object

不幸的是,.NET 並沒有提供一種方法來實現這一點。

為了解決這個問題,我創建了 OSS 庫Genumerics ,它為以下內置數字類型及其可為空的等效項提供大多數標准數字操作,並能夠添加對其他數字類型的支持。

sbytebyteshortushortintuintlongulongfloatdoubledecimalBigInteger

性能相當於數字類型特定的解決方案,允許您創建高效的通用數字算法。

這是代碼使用的示例。

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;
}

目前還沒有“好的”解決方案。 但是,您可以顯着縮小類型參數,以排除您假設的“INumeric”約束的許多不匹配,如 Haacked 上面所示。

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

.NET 數字基元類型不共享任何允許它們用於計算的通用接口。 可以定義您自己的接口(例如ISignedWholeNumber )來執行此類操作,定義包含單個Int16Int32等的結構並實現這些接口,然后具有接受泛型類型的方法,這些泛型類型約束為ISignedWholeNumber ,但具有將數值轉換為您的結構類型可能會很麻煩。

另一種方法是使用靜態屬性bool Available {get;};定義靜態類Int64Converter<T> Int64 GetInt64(T value)T FromInt64(Int64 value)bool TryStoreInt64(Int64 value, ref T dest)的靜態委托。 類構造函數可以使用硬編碼來加載已知類型的委托,並可能使用反射來測試類型T是否實現具有正確名稱和簽名的方法(如果它類似於包含Int64並表示數字的結構,但有一個自定義ToString()方法)。 這種方法將失去與編譯時類型檢查相關的優勢,但仍會設法避免裝箱操作,並且每種類型只需“檢查”一次。 之后,與該類型關聯的操作將被委托調度替換。

我會使用一個通用的,你可以處理外部...

/// <summary>
/// Generic object copy of the same type
/// </summary>
/// <typeparam name="T">The type of object to copy</typeparam>
/// <param name="ObjectSource">The source object to copy</param>
public T CopyObject<T>(T ObjectSource)
{
    T NewObject = System.Activator.CreateInstance<T>();

    foreach (PropertyInfo p in ObjectSource.GetType().GetProperties())
        NewObject.GetType().GetProperty(p.Name).SetValue(NewObject, p.GetValue(ObjectSource, null), null);

    return NewObject;
}

練習的重點是什么?

正如人們已經指出的那樣,您可以使用一個非泛型函數來獲取最大的項目,編譯器會自動為您轉換較小的整數。

static bool IntegerFunction(Int64 value) { }

如果您的函數在性能關鍵路徑上(極不可能,IMO),您可以為所有需要的函數提供重載。

static bool IntegerFunction(Int64 value) { }
...
static bool IntegerFunction(Int16 value) { }

當我嘗試為泛型類型重載運算符時,這個限制影響了我。 由於沒有“INumeric”約束,並且由於一系列其他原因,stackoverflow 上的好人很樂意提供,因此無法在泛型類型上定義操作。

我想要類似的東西

public struct Foo<T>
{
    public T Value{ get; private set; }

    public static Foo<T> operator +(Foo<T> LHS, Foo<T> RHS)
    {
        return new Foo<T> { Value = LHS.Value + RHS.Value; };
    }
}

我已經使用 .net4 動態運行時類型解決了這個問題。

public struct Foo<T>
{
    public T Value { get; private set; }

    public static Foo<T> operator +(Foo<T> LHS, Foo<T> RHS)
    {
        return new Foo<T> { Value = LHS.Value + (dynamic)RHS.Value };
    }
}

關於使用dynamic的兩件事是

  1. 表現。 所有值類型都被裝箱。
  2. 運行時錯誤。 您“擊敗”了編譯器,但失去了類型安全性。 如果泛型類型沒有定義操作符,執行過程中會拋出異常。

如果您只想使用一種數字類型,您可以考慮using創建類似於 C++ 中的別名的東西。

因此,與其擁有非常通用的

T ComputeSomething<T>(T value1, T value2) where T : INumeric { ... }

你可以有

using MyNumType = System.Double;
T ComputeSomething<MyNumType>(MyNumType value1, MyNumType value2) { ... }

如果需要,這可能使您可以輕松地從double轉到int或其他,但是您將無法在同一程序ComputeSomethingdoubleint一起使用。

但是為什么不把所有的double都替換為int呢? 因為無論輸入是double還是int ,您的方法都可能希望使用double 別名使您可以准確地知道哪個變量使用動態類型。

所有數字類型都是實現IComparable, IComparable<T>, IConvertible, IEquatable<T>, IFormattable 但是, DateTime也是如此。

所以這個通用的擴展方法是可能的:

public static bool IsNumeric<T>(this T value) where T : struct, IComparable, IComparable<T>, IConvertible, IEquatable<T>, IFormattable =>
  typeof(T) != typeof(DateTime);

但是對於實現這些接口的結構,它將失敗,例如:

public struct Foo : IComparable, IComparable<Foo>, IConvertible, IEquatable<Foo>, IFormattable { /* ... */ }

這種非通用替代方案的性能較低,但可以保證工作:

public static bool IsNumeric(this Type type) =>
  type == typeof(sbyte) || type == typeof(byte) ||
  type == typeof(short) || type == typeof(ushort) ||
  type == typeof(int) || type == typeof(uint) ||
  type == typeof(long) || type == typeof(ulong) ||
  type == typeof(float) ||
  type == typeof(double) ||
  type == typeof(decimal);

它們都沒有繼承任何單一的接口或基類(其他類也沒有繼承),因此簡單的答案是“否”。

我確實想知道為什么這是一個問題。 您想在IntegerFunction類中只能做整數的事情是什么?

我認為您是對仿制葯的誤解。 如果您嘗試執行的操作僅對特定數據類型有用,那么您就沒有在做“泛型”的事情。

另外,由於您只希望允許該函數在int數據類型上工作,因此您不需要為每個特定大小使用單獨的函數。 只需采用最大特定類型的參數,程序便可以自動將較小的數據類型轉換為該參數。 (即,調用時傳遞Int16將自動轉換為Int64)。

如果您根據傳遞給函數的int的實際大小執行不同的操作,那么我認為即使嘗試做您正在做的事情,您也應該認真考慮一下。 如果您必須愚弄語言,則應多想一些您想完成的事情,而不是如何做自己想做的事情。

如果沒有其他方法,則可以使用Object類型的參數,然后必須檢查參數的類型並采取適當的措施或引發異常。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM