ジェネリックメソッドを数値型に制限する制約はありますか?


364

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

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

whereキーワードは承知していますが、専用のインターフェースが見つかりませんこれらのタイプ。

何かのようなもの:

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

回答:


140

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

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

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

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

GitHub Gistでの最小限の実装。

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

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

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

残念ながら同じ制限を共有する別の方法は、セルゲイシャンダーの回答で説明されているように、ポリシークラスを使用することです。


25
ところで、MiscUtilはまさにこれを行うジェネリッククラスを提供します。Operator/ Operator<T>; yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html
Marc Gravell

1
@Mark:良いコメント。ただし、明確にするために、Hejlsbergがコード生成と同じように問題の解決策として言及していたとは思いませんOperator<T>Expressionsフレームワークが存在するずっと前にインタビューが行われたためです)もちろん使用Reflection.Emit)–そして彼の回避策に本当に興味があります。
Konrad Rudolph、

@Konrad Rudolph:同様の質問に対するこの回答がHejlsbergの回避策を説明していると思います。他のジェネリッククラスは抽象化されます。サポートするタイプごとに他のジェネリッククラスを実装する必要があるため、コードが重複しますが、インスタンス化できるのは、サポートされているタイプの元のジェネリッククラスのみです。
Ergwun

14
私はHeijsbergの言い回しに同意しません。それは本当にC#を宣伝するマーケティングBSです。強い/弱いタイピングは、診断の質とは関係ありません。それ以外の場合:興味深い発見。
セバスチャンマッハ

100

この質問の人気とそのような関数の背後にある関心を考えると、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();
        }
    }
}

ここに画像の説明を入力してください

私は1つの発言を先に取得します。いいえ、これはDRY原則の違反ではありません。DRYの原則は、アプリケーションの保守が困難になる複数の場所でコードが重複しないようにすることです。

これはすべての場合に当てはまるわけではありません。変更が必要な場合は、テンプレート(すべての世代の単一のソース!)を変更するだけで完了です。

独自のカスタム定義で使用するには、生成されたコードに名前空間宣言(独自の実装を定義するものと同じであることを確認)を追加し、クラスをとしてマークしますpartial。その後、これらの行をテンプレートファイルに追加して、最終的なコンパイルに含まれるようにします。

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

正直に言いましょう:これはかなりクールです。

免責事項:このサンプルは、Manning PublicationsのKevin HazzardとJason Bockによる.NETのメタプログラミングの影響を強く受けています


これはかなりクールですが、このソリューションを変更して、メソッドTがさまざまなIntXクラスである、またはさまざまなクラスから継承するジェネリック型を受け入れるようにすることは可能でしょうか?時間を節約できるのでこの解決策が好きですが、100%解決すると問題が解決します(C#がこのタイプの制約をサポートしているかのように組み込まれているわけではありませんが)生成された各メソッドはまだ汎用的であるため、IntXXクラスの1つから継承するタイプのオブジェクトを返すことができます。
Zachary Kniebel 2014年

1
@ZacharyKniebel:IntXXタイプは構造体です。つまり、そもそも継承をサポートしていません。そして、そうした場合でも、Liskov置換の原則(SOLIDイディオムから知っているかもしれません)が適用されます。メソッドがそのように定義されXYその子であるX場合、定義ごとYに、そのメソッドにその基本タイプ。
Jeroen Vannevel 14年

1
ポリシーstackoverflow.com/questions/32664/…を使用したこの回避策は、T4を使用してクラスを生成します。
Sergey Shandar 14

2
ポリシーベースのソリューションとは異なり、組み込みの整数型の操作効率を維持するため、このソリューションの+1。追加の(おそらく仮想)メソッドを介して組み込みのCLR演算子(Addなど)を呼び出すと、(数学ライブラリのように)何度も使用するとパフォーマンスに重大な影響を与える可能性があります。また、整数型の数は一定であるため(継承できないため)、バグを修正するためにコードを再生成するだけで済みます。
Attila Klenik 2016

1
とてもクールで、それを使い始めようとしていたところ、リファクタリングがResharperにどれほど依存しているかを思い出しました。T4テンプレートを使用してリファクタリングを変更することはできません。重要ではありませんが、検討する価値があります。
bradgonesurfing

86

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

私はさらに進んで、私たちは必要だと言います

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

あるいは

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

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

あなたはcodeprojectでこのINullable<T>ような他の何かに(のように)数をラップすることができます。


実行時に制限を適用することもできます(演算子を反映したり、型をチェックしたりすることにより)が、そもそもジェネリックを持つ利点が失われます。


2
MiscUtilが汎用演算子をサポートしているのを見たことがありますか... yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html
Marc Gravell

10
ええ-ジョン・スキートが少し前に別の場所で私にそれらを指摘しました(しかし、今年の古い応答の後)-それらは巧妙なアイデアですが、適切な制約のサポートが欲しいです。
キース、

1
待って、where T : operators( +, -, /, * )合法的なC#ですか?初心者の質問でごめんなさい。
kdbanman 2015

@kdbanman私はそうは思いません。キースは、C#はOPが要求することをサポートしていないと言ってwhere T : operators( +, -, /, * )おり、できるはずですができないことを示唆しています。
AMTerp

62

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

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


ジェネリックパラメーターよりも関数の引数が少ない場合、このパターンで問題が発生しました。オープンstackoverflow.com/questions/36048248/...
xvan

なぜ使用するのstructですか?代わりにシングルトンクラスを使用しpublic static NumericPolicies Instance = new NumericPolicies();、インスタンスをに変更して、このコンストラクタを追加するとどうなりますかprivate NumericPolicies() { }
M.kazem Akhgary 2016

@ M.kazemAkhgaryシングルトンを使用できます。私は構造体を好む。構造体には情報が含まれていないため、理論的にはコンパイラ/ CLRで最適化できます。シングルトンの場合でも、リファレンスを渡すため、GCに追加の圧力がかかる場合があります。別の利点は、構造体をnullにすることはできないということです:-)。
Sergey Shandar

私はあなたが非常にスマートな解決策を見つけたと言うつもりだったが、解決策は、私にとっても限定されている:私はそれを使用するつもりだったT Add<T> (T t1, T t2)が、Sum()それはそれは不可能であることのパラメータからTの独自のタイプだ取り出すことができたときにのみ動作します別のジェネリック関数に埋め込まれている場合。
Tobias Knauss

16

この質問はFAQの質問の1つなので、これをwikiとして投稿します(以前に同様に投稿したことがあるので、これは古い質問です)。とにかく...

どのバージョンの.NETを使用していますか?.NET 3.5を使用している場合は、MiscUtil(無料など)に汎用演算子の実装があります。

これにはT Add<T>(T x, T y)、のようなメソッドと、さまざまな型(のようなDateTime + TimeSpan)での算術の他のバリアントがあります。

さらに、これはすべての組み込み、リフト、オーダーメイドのオペレーターで機能し、パフォーマンスのためにデリゲートをキャッシュします。

これがトリッキーである理由のいくつかの追加の背景はここにあります

dynamic(4.0)sort-ofがこの問題を間接的に解決することも知りたいかもしれません-すなわち

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

14

残念ながら、このインスタンスでは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...
}

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

また、この実装で考えられるパフォーマンスへの影響についても調べます。おそらく、もっと速い方法があるでしょう。


13
+1ただし、// Rest of code...制約によって定義された操作に依存している場合はコンパイルされない可能性があります。
Nick

1
Convert.ToIntXX(value)は、「// Rest of code」をコンパイルするのに役立つ可能性があります-少なくとも、IntegerFunctionの戻り値の型がT型になるまでは、フックされます。:-p
ヨーヨー

-1; これは、@ Nickによって与えられた理由で機能しません。// Rest of code...like value + valueまたはvalue * valueで算術演算を実行しようとすると、コンパイルエラーが発生します。
Mark Amery 2017年

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>

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


6

C#7.3以降では、あなたがより近く使用することができ近似 - 管理対象外の制約を型パラメータには非ポインタ、非NULL可能であることを指定するために管理されていないタイプ。

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

アンマネージ制約は構造体制約を意味し、構造体制約またはnew()制約と組み合わせることはできません。

タイプは、次のタイプの場合、アンマネージタイプです。

  • sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal、またはbool
  • 列挙型
  • ポインタ型
  • アンマネージタイプのフィールドのみを含み、C#7.3以前では構成タイプ(少なくとも1つのタイプ引数を含むタイプ)ではないユーザー定義の構造体タイプ

さらに制限し、IComparableを追加実装していないポインタとユーザー定義型なくすためにIComparableをするので、IEquatable <T>を追加することにより、列挙型を制限し、(しかし、列挙型はまだIComparableを由来し、あなたの状況に応じて、さらに行くと追加のインタフェースを追加することができます。管理されていない場合、このリストを短く保つことができます):

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

ニースではなく、十分な...例えば、DateTime該当unmanaged, IComparable, IEquatable<T>..制約
アダム・カルベBohl

私は知っていますが、状況に応じてさらに進んで、インターフェイスを追加できます。アンマネージでは、このリストを短くすることができます。アンマネージドを使用した近似、アプローチを示しました。ほとんどの場合これで十分です
Vlad Novakovsky

4

テンプレートをタイプに制限する方法はありませんが、タイプに基づいてさまざまなアクションを定義できます。汎用数値パッケージの一部として、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;
        }

実証的なソリューションを提供していただきありがとうございます。
zsf222 2015年

タイプごとに同じメソッドを作成するのと同じではありませんか?
ルイス・

3

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

の代わりに:

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


2

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

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


2

これに対する「良い」解決策はまだありません。ただし、Haackedが上記に示したように、型引数を大幅に絞り込んで、仮想の「数値」制約の多くのミスフィットを除外できます。

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


2

.NET 4.0以降を使用している場合は、メソッドの引数としてdynamicを使用し、実行時に、渡された動的引数の型が数値型または整数型であることを確認できます

合格のタイプならば、動的であるない数値/整数型は、例外をスローします。

アイデアを実装する短いコードの例は次のようなものです。

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より下/下/未満であり、そのバージョンで動的が定義されていない場合は、代わりにオブジェクトを使用して整数型にキャストする必要があります。これは問題があるため、可能であれば、少なくとも.NET 4.0以降。オブジェクトの代わりに動的を使用できます


2

残念ながら、.NETはそれをネイティブに行う方法を提供していません。

この問題に対処するために、私はOSSライブラリGenumericsを作成しました。これは、以下の組み込み数値型およびそれらのNULL可能同等物に最も標準的な数値演算を提供し、他の数値型のサポートを追加する機能を備えています。

sbytebyteshortushortintuintlongulongfloatdoubledecimal、およびBigInteger

パフォーマンスは数値タイプ固有のソリューションと同等であり、効率的な汎用数値アルゴリズムを作成できます。

次にコードの使用例を示します。

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

1

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

人々がすでに指摘したように、あなたは最大でないアイテムを取る非ジェネリック関数を持つことができ、コンパイラは自動的に小さな整数を変換します。

static bool IntegerFunction(Int64 value) { }

関数がパフォーマンスクリティカルパス上にある場合(IMOの可能性は非常に低い)、必要なすべての関数にオーバーロードを提供できます。

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

1
私は数値計算をよく扱っています。整数が必要な場合もあれば、浮動小数点が必要な場合もあります。どちらにも64ビットバージョンがあり、処理速度に最適です。これらの間で変換することは、どちらの方法にも損失があるため、ひどい考えです。私は倍精度浮動小数点数を使用する傾向がありますが、整数が他の場所でどのように使用されるかを考えると、整数を使用する方がよい場合があります。ただし、アルゴリズムを1回だけ作成し、型の決定をインスタンスの要件に任せる場合は、非常に便利です。
VoteCoffee 2014年

1

私はあなたが外部的に処理できる一般的なものを使います...

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

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. ランタイムエラー。コンパイラを「打ち負かす」が、型安全性を失う。ジェネリック型に演算子が定義されていない場合、実行中に例外がスローされます。

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)。クラスコンストラクターをハードコードして既知の型のデリゲートをロードし、場合によってはリフレクションを使用Tして、型が適切な名前とシグネチャでメソッドを実装しているかどうかをテストできます(これは、を含み、Int64数値を表す構造体のようなものですが、カスタムToString()メソッド)。このアプローチでは、コンパイル時の型チェックに関連する利点が失われますが、ボックス化操作を回避でき、各型を1回だけ「チェック」するだけで済みます。その後、そのタイプに関連付けられた操作は、デリゲートディスパッチに置き換えられます。


@KenKin:IConvertibleは、任意の整数を別の整数型に追加してInt64結果などを生成する手段を提供しますが、たとえば任意の型の整数をインクリメントして同じ型の別の整数を生成する手段を提供しません。
スーパーキャット2013

1

同様の状況で、数値型と文字列を処理する必要がありました。少し奇妙なミックスのようですが、そこに行きます。

繰り返しますが、多くの人々と同様に、私は制約を検討し、それがサポートしなければならないたくさんのインターフェースを思いつきました。ただし、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
}

0

必要なのが1つの数値型を使用することだけである場合は、C ++でと同様のエイリアスを作成することを検討できますusing

したがって、非常に一般的な

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

あなたが持つことができた

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

それはあなたが簡単に行くことができかもしれないdoubleint、必要な場合等がありますが、使用することはできないだろうComputeSomethingdoubleし、int同じプログラムで。

しかし、なぜそれdoubleまですべてを置き換えないのintですか?あなたの方法は、使用したい場合があるのでdouble、入力があるかどうdoubleint。エイリアスにより、動的変数を使用する変数を正確に知ることができます。


0

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

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

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

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

したがって、まだ少し待つ必要がありますが、リリース後は次のように実行できます。

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

-11

ジェネリックを誤解していると思います。実行しようとしている操作が特定のデータ型に対してのみ有効である場合は、「一般的な」処理を行っていません。

また、関数がintデータ型で機能することを許可するだけなので、特定のサイズごとに個別の関数を使用する必要はありません。最大の特定の型のパラメーターを取るだけで、プログラムは自動的に小さいデータ型をそれにアップキャストできます。(つまり、Int16を渡すと、呼び出し時にInt64に自動変換されます)。

関数に渡されるintの実際のサイズに基づいて異なる操作を実行している場合は、自分がやっていることを真剣に再検討する必要があると思います。言語をだます必要がある場合は、やりたいことをする方法ではなく、達成しようとしていることについてもう少し考えるべきです。

それ以外の場合は、Object型のパラメーターを使用できます。パラメーターの型を確認し、適切なアクションを実行するか、例外をスローする必要があります。


10
クラスHistogram <T>を考えます。ジェネリックパラメーターを取ることは理にかなっているので、コンパイラーはバイト、int、double、decimal、BigIntなどに対して最適化できますが、たとえば、Histogram <Hashsetを作成できないようにする必要があります。 >、なぜなら-Tronと話すと、計算されないからです。(文字通り:))
10

15
ジェネリックを誤解しているのはあなたです。メタプログラミングは、考えられる型である可能性がある値を操作するだけではなく、さまざまな制約に適合する型を操作するためのものです。
ジムバルター2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.