427

ジェネリックを使用してジェネリック型引数Tをのみに制限する方法があるかどうか、誰でも教えてもらえますか:

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

キーワードは知っていますが、これらの型だけwhereのインターフェイスが見つかりません。

何かのようなもの:

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

24 に答える 24

165

C# はこれをサポートしていません。Hejlsberg は、Bruce Eckel とのインタビューで、この機能を実装しない理由を次のように説明しています。

そして、追加された複雑さが、得られるわずかな収量に見合う価値があるかどうかは明らかではありません. やりたいことが制約システムで直接サポートされていない場合は、ファクトリ パターンを使用して実行できます。Matrix<T>たとえば、Matrix内積法を定義したいand を持つことができます。もちろん、それは最終的に 2 つTの s を乗算する方法を理解する必要があることを意味しますが、少なくともTis intdouble、またはの場合は、それを制約として言うことはできませんfloat。しかし、あなたができることはMatrix、引数としてあなたのテイクを持ちCalculator<T>、 で、 とCalculator<T>呼ばれるメソッドを持つことmultiplyです。それを実装して、に渡しますMatrix

ただし、これはかなり複雑なコードにつながり、ユーザーは使用したいものごとに独自のCalculator<T>実装を提供する必要がありTます。拡張可能である必要がない限り、つまり、 や などの固定数の型をサポートしたいだけであればintdouble比較的単純なインターフェースを使用できます。

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

( GitHub Gist での最小限の実装。 )

ただし、ユーザーが独自のカスタム型を提供できるようにしたい場合は、すぐにこの実装を開いて、ユーザーが独自のCalculatorインスタンスを提供できるようにする必要があります。たとえば、カスタム 10 進浮動小数点実装を使用する行列をインスタンス化するには、次のDFPコードを記述する必要があります。

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

…そして のすべてのメンバーを実装しDfpCalculator : ICalculator<DFP>ます。

残念ながら同じ制限を共有する別の方法は、Sergey Shandar's answer で説明されているように、ポリシー クラスを使用することです。

于 2008-08-29T08:38:25.963 に答える
121

この質問の人気とそのような機能の背後にある関心を考えると、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 による Metaprogramming in .NET の影響を強く受けています。

于 2014-03-15T14:30:53.097 に答える
96

これには制約はありません。数値計算にジェネリックを使用したい人にとっては、これは本当の問題です。

さらに進んで、必要だと言います

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

あるいは

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

残念ながら、インターフェイス、基本クラス、およびキーワードstruct(値型である必要があります)、class(参照型であるnew()必要があります)、および (デフォルトのコンストラクターが必要です) しかありません。

ここの codeprojectINullable<T>のように、数値を別のもの ( と同様)でラップすることができます。


実行時に制限を適用することもできますが (演算子をリフレクトするか、型をチェックすることにより)、そもそもジェネリックを使用する利点が失われます。

于 2008-08-28T16:11:43.620 に答える
66

ポリシーを使用した回避策:

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構造体を参照してください。

于 2011-01-28T23:38:27.810 に答える
15

残念ながら、このインスタンスの where 句では構造体しか指定できません。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...
}

これは私が知っている少し醜いですが、少なくとも必要な制約を提供します.

また、この実装のパフォーマンスへの影響の可能性についても調べます。おそらくもっと速い方法があります。

于 2008-08-28T16:24:34.150 に答える
13

おそらくあなたができる最も近いのは

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>

非常に具体的なものについては、各タイプのオーバーロードだけではなく、リストが非常に短く、メモリ フットプリントが少ない可能性があります。

于 2008-08-28T16:12:33.070 に答える
11

トピックは古いですが、将来の読者のために:

Discriminated Unionsこの機能は、これまで C# に実装されていないものと密接に関連しています。ここでその問題を見つけました:

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

この問題はまだ未解決であり、機能が計画されていますC# 10

まだもう少し待つ必要がありますが、リリース後は次の方法で実行できます。

static bool IntegerFunction<T>(T value) where T : Int16 | Int32 | Int64 | ...
于 2020-06-05T08:35:28.577 に答える
5

テンプレートをタイプに制限する方法はありませんが、タイプに基づいてさまざまなアクションを定義できます。汎用数値パッケージの一部として、2 つの値を加算するための汎用クラスが必要でした。

    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;
        }
于 2014-12-23T19:53:38.100 に答える
4

これらの問題を解決するために、小さなライブラリ機能を作成しました。

それ以外の:

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

于 2013-05-10T11:15:05.497 に答える
2

これに対する「良い」解決策はまだありません。ただし、Haackedが上に示したように、type引数を大幅に絞り込んで、仮想の「INumeric」制約に対する多くの不適合を除外することができます。

static bool IntegerFunction <T>(T value)ここで、T:IComparable、IFormattable、IConvertible、IComparable <T>、IEquatable <T>、struct {.. ..

于 2011-07-01T15:08:40.427 に答える
2

私は samjudson と同じことを考えていましたが、なぜ整数だけなのですか? その場合は、ヘルパー クラスなどを作成して、必要なすべての型を保持することをお勧めします。

整数のみが必要な場合は、ジェネリックを使用しないでください。ジェネリックではありません。またはさらに良いことに、そのタイプをチェックして他のタイプを拒否します。

于 2008-08-28T16:14:20.900 に答える
1

この制限は、ジェネリック型の演算子をオーバーロードしようとしたときに影響を受けました。「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 };
    }
}

使用についての2つのことdynamic

  1. パフォーマンス。すべての値型がボックス化されます。
  2. ランタイムエラー。コンパイラを「打ち負かす」が、型の安全性を失う。ジェネリック型に演算子が定義されていない場合、実行中に例外がスローされます。
于 2010-11-15T20:52:21.660 に答える
1

演習のポイントは何ですか?

すでに指摘されているように、非ジェネリック関数が最大のアイテムを取得する可能性があり、コンパイラーは自動的に小さなintを変換します。

static bool IntegerFunction(Int64 value) { }

関数がパフォーマンスクリティカルパス上にある場合(ほとんどありませんが、IMO)、必要なすべての関数にオーバーロードを提供できます。

static bool IntegerFunction(Int64 value) { }
...
static bool IntegerFunction(Int16 value) { }
于 2008-08-28T20:59:00.667 に答える
1

I would use a generic one which you could handle externaly...

/// <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;
}
于 2010-01-14T15:53:39.040 に答える
1

.NET 数値プリミティブ型は、計算に使用できる共通のインターフェイスを共有していません。そのような操作を実行する独自のインターフェイス (例: ISignedWholeNumber) を定義し、単一Int16Int32、 などを含む構造を定義してそれらのインターフェイスを実装し、 に制約されたジェネリック型を受け入れるメソッドをISignedWholeNumber持つことができますが、数値を変換する必要があります。あなたの構造型に迷惑をかける可能性があります。

Int64Converter<T>別のアプローチは、静的プロパティと、、、bool Available {get;};の静的デリゲートを使用して静的クラスを定義することです。クラス コンストラクターは、既知の型のデリゲートをロードするためにハードコーディングすることができます。また、リフレクションを使用して、型が適切な名前とシグネチャを持つメソッドを実装しているかどうかをテストすることもできます (それが を含み、数値を表す構造体のようなものである場合)。カスタムメソッド)。このアプローチでは、コンパイル時の型チェックに関連する利点が失われますが、ボックス化操作を回避することができ、各型を一度だけ「チェック」する必要があります。その後、その型に関連付けられた操作はデリゲート ディスパッチに置き換えられます。Int64 GetInt64(T value)T FromInt64(Int64 value)bool TryStoreInt64(Int64 value, ref T dest)TInt64ToString()

于 2013-05-06T15:44:04.410 に答える