null可能なすべてのC#ジェネリック型制約


110

だから私はこのクラスを持っています:

public class Foo<T> where T : ???
{
    private T item;

    public bool IsNull()
    {
        return item == null;
    }

}

今私は私がすることができる型パラメーターとしてすべてを使用できるようにする型制約を探していますnull。つまり、すべての参照タイプとすべてのNullableT?)タイプを意味します。

Foo<String> ... = ...
Foo<int?> ... = ...

可能なはずです。

使用するclassタイプの制約としては、私だけが参照型を使用することができます。

追加情報: パイプとフィルターのアプリケーションを作成nullしていて、パイプラインに渡される最後のアイテムとして参照を使用して、すべてのフィルターが適切にシャットダウンしたり、クリーンアップしたりできるようにしたい...


1
Nullableを許可しない@Tim
Rik

このリンクはあなたを助けることがあります。social.msdn.microsoft.com/Forums/en-US/...
レダMattar

2
これを直接行うことはできません。おそらくあなたはあなたのシナリオについてもっと教えてもらえますか?またはIFoo<T>、作業タイプとして使用し、ファクトリメソッドを介してインスタンスを作成できますか?それはうまくいくかもしれません。
2013年

なぜあなたが何かをこのように制限したいのか、または必要とするのか、私にはわかりません。あなたの唯一の意図が「if x == null」をif x.IsNull()に変換することである場合、これは前の構文に慣れている99.99%の開発者にとっては無意味で直感的ではないようです。コンパイラーはあなたに「もしあなたがすでにカバーされているので、とにかく(int型)のx == nullの」、。
RJローハン

回答:


22

コンパイル時チェックではなくFooのコンストラクターで実行時チェックを行う場合は、型が参照型またはnull許容型でないかどうかを確認し、そうであれば例外をスローできます。

ランタイムチェックだけでは受け入れられないかもしれませんが、念のため:

public class Foo<T>
{
    private T item;

    public Foo()
    {
        var type = typeof(T);

        if (Nullable.GetUnderlyingType(type) != null)
            return;

        if (type.IsClass)
            return;

        throw new InvalidOperationException("Type is not nullable or reference type.");
    }

    public bool IsNull()
    {
        return item == null;
    }
}

その後、次のコードはコンパイルされますが、最後のコード(foo3)はコンストラクターで例外をスローします。

var foo1 = new Foo<int?>();
Console.WriteLine(foo1.IsNull());

var foo2 = new Foo<string>();
Console.WriteLine(foo2.IsNull());

var foo3= new Foo<int>();  // THROWS
Console.WriteLine(foo3.IsNull());

31
これを行う場合は、必ず静的コンストラクターでチェックを行ってください。そうしないと、ジェネリッククラスのすべてのインスタンスの構築が遅くなります(不必要に)
Eamon Nerbonne

2
@EamonNerbonne静的コンストラクターから例外を発生させないでください
Matthew Watson

5
ガイドラインは絶対的なものではありません。このチェックが必要な場合は、実行時チェックのコストと静的コンストラクターでの例外の扱いにくさをトレードオフする必要があります。ここでは貧乏人の静的アナライザーを実際に実装しているので、開発中を除いて、この例外がスローされることはありません。最後に、すべてのコストで静的構築の例外を回避したい場合でも(賢明ではありません)、インスタンスコンストラクターで可能な限り静的かつ可能な限り多くの作業を行う必要があります。たとえば、 "isBorked"フラグなどを設定します。
Eamon Nerbonne 2017年

ちなみに、これをやってみようとは全然思わない。ほとんどの状況で、私はこれをC#の制限として受け入れることを好みますが、リークしやすく、失敗しやすい抽象化を使用して作業するのではありません。たとえば、別の解決策としては、クラスのみを必要とする、または構造体のみを必要とする(そして明示的にemをnull可能にする)こと、または両方を実行して2つのバージョンを持つことが考えられます。これはこのソリューションに対する批判ではありません。これは、この問題をうまく解決できないということだけです。つまり、カスタムroslynアナライザーを作成するつもりがない限りです。
Eamon Nerbonne 2017年

1
両方の長所を手に入れることができます- static bool isValidType静的コンストラクターで設定したフィールドを保持し、インスタンスコンストラクターでそのフラグをチェックし、それが無効なタイプである場合はスローするだけで、構築するたびにすべてのチェック作業を実行しませんインスタンス。私はこのパターンをよく使用します。
Mike Marynowski、2018

20

ジェネリックでORに相当するものを実装する方法がわかりません。ただし、null許容型のnullと構造体の0値を作成するために、デフォルトのキーワードを使用することを提案できます。

public class Foo<T>
{
    private T item;

    public bool IsNullOrDefault()
    {
        return Equals(item, default(T));
    }
}

Nullableのバージョンを実装することもできます。

class MyNullable<T> where T : struct
{
    public T Value { get; set; }

    public static implicit operator T(MyNullable<T> value)
    {
        return value != null ? value.Value : default(T);
    }

    public static implicit operator MyNullable<T>(T value)
    {
        return new MyNullable<T> { Value = value };
    }
}

class Foo<T> where T : class
{
    public T Item { get; set; }

    public bool IsNull()
    {
        return Item == null;
    }
}

例:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(new Foo<MyNullable<int>>().IsNull()); // true
        Console.WriteLine(new Foo<MyNullable<int>> {Item = 3}.IsNull()); // false
        Console.WriteLine(new Foo<object>().IsNull()); // true
        Console.WriteLine(new Foo<object> {Item = new object()}.IsNull()); // false

        var foo5 = new Foo<MyNullable<int>>();
        int integer = foo5.Item;
        Console.WriteLine(integer); // 0

        var foo6 = new Foo<MyNullable<double>>();
        double real = foo6.Item;
        Console.WriteLine(real); // 0

        var foo7 = new Foo<MyNullable<double>>();
        foo7.Item = null;
        Console.WriteLine(foo7.Item); // 0
        Console.WriteLine(foo7.IsNull()); // true
        foo7.Item = 3.5;
        Console.WriteLine(foo7.Item); // 3.5
        Console.WriteLine(foo7.IsNull()); // false

        // var foo5 = new Foo<int>(); // Not compile
    }
}

フレームワークの元のNullable <T>は構造体であり、クラスではありません。値型を模倣する参照型ラッパーを作成することは良い考えではないと思います。
Niall Connaughton

1
デフォルトを使用する最初の提案は完璧です!これで、返されるジェネリック型を持つテンプレートは、オブジェクトのnullと組み込み型のデフォルト値を返すことができます。
Casey Anderson、

13

私はこの問題に遭遇し、「null可能」なもの(参照型またはNull可能)をとることができる汎用の静的メソッドが必要だったため、満足のいく解決策がなかったため、この問題が発生しました。単純に2つのオーバーロードされたメソッド、かかる有するものでOPの規定の問題よりも解決することは比較的簡単だった私自身の解決策を考え出した私はそうTと制約があるwhere T : classかかり、別のT?と持っているがwhere T : struct

次に、このソリューションをこの問題に適用して、コンストラクターをプライベート(または保護)にし、静的なファクトリメソッドを使用することで、コンパイル時にチェック可能なソリューションを作成できることにも気付きました。

    //this class is to avoid having to supply generic type arguments 
    //to the static factory call (see CA1000)
    public static class Foo
    {
        public static Foo<TFoo> Create<TFoo>(TFoo value)
            where TFoo : class
        {
            return Foo<TFoo>.Create(value);
        }

        public static Foo<TFoo?> Create<TFoo>(TFoo? value)
            where TFoo : struct
        {
            return Foo<TFoo?>.Create(value);
        }
    }

    public class Foo<T>
    {
        private T item;

        private Foo(T value)
        {
            item = value;
        }

        public bool IsNull()
        {
            return item == null;
        }

        internal static Foo<TFoo> Create<TFoo>(TFoo value)
            where TFoo : class
        {
            return new Foo<TFoo>(value);
        }

        internal static Foo<TFoo?> Create<TFoo>(TFoo? value)
            where TFoo : struct
        {
            return new Foo<TFoo?>(value);
        }
    }

これで、次のように使用できます。

        var foo1 = new Foo<int>(1); //does not compile
        var foo2 = Foo.Create(2); //does not compile
        var foo3 = Foo.Create(""); //compiles
        var foo4 = Foo.Create(new object()); //compiles
        var foo5 = Foo.Create((int?)5); //compiles

パラメーターなしのコンストラクターが必要な場合は、オーバーロードの便利さは得られませんが、次のようなことができます。

    public static class Foo
    {
        public static Foo<TFoo> Create<TFoo>()
            where TFoo : class
        {
            return Foo<TFoo>.Create<TFoo>();
        }

        public static Foo<TFoo?> CreateNullable<TFoo>()
            where TFoo : struct
        {
            return Foo<TFoo?>.CreateNullable<TFoo>();
        }
    }

    public class Foo<T>
    {
        private T item;

        private Foo()
        {
        }

        public bool IsNull()
        {
            return item == null;
        }

        internal static Foo<TFoo> Create<TFoo>()
            where TFoo : class
        {
            return new Foo<TFoo>();
        }

        internal static Foo<TFoo?> CreateNullable<TFoo>()
            where TFoo : struct
        {
            return new Foo<TFoo?>();
        }
    }

次のように使用します。

        var foo1 = new Foo<int>(); //does not compile
        var foo2 = Foo.Create<int>(); //does not compile
        var foo3 = Foo.Create<string>(); //compiles
        var foo4 = Foo.Create<object>(); //compiles
        var foo5 = Foo.CreateNullable<int>(); //compiles

このソリューションにはいくつかの欠点があります。1つは、オブジェクトの構築に「新規」を使用することを好む場合があることです。もう1つは、次Foo<T>のような型制約のジェネリック型引数として使用できないことですwhere TFoo: new()。最後に、ここで必要な追加のコードが少しあります。これは、特に複数のオーバーロードされたコンストラクタが必要な場合に増加します。


8

前述のように、コンパイル時にチェックすることはできません。.NETの一般的な制約は大幅に不足しており、ほとんどのシナリオをサポートしていません。

ただし、これは実行時チェックのためのより良いソリューションであると私は考えています。どちらも定数なので、JITコンパイル時に最適化できます。

public class SomeClass<T>
{
    public SomeClass()
    {
        // JIT-compile time check, so it doesn't even have to evaluate.
        if (default(T) != null)
            throw new InvalidOperationException("SomeClass<T> requires T to be a nullable type.");

        T variable;
        // This still won't compile
        // variable = null;
        // but because you know it's a nullable type, this works just fine
        variable = default(T);
    }
}

3

そのような型制約は不可能です。型制約ドキュメントによると、null許容型と参照型の両方をキャプチャする制約はありません。制約は結合でのみ組み合わせることができるため、組み合わせによってそのような制約を作成する方法はありません。

ただし、常に== nullを確認できるため、ニーズに応じて制約なしの型パラメーターにフォールバックできます。タイプが値タイプの場合、チェックは常にfalseと評価されます。次に、セマンティクスが適切である限り、重要ではないR#警告「値型とnullの比較が可能です」が表示されます。

別の方法としては、

object.Equals(value, default(T))

nullチェックの代わりに、default(T)でT:クラスは常にnullです。ただし、これは、null可能ではない値が明示的に設定されたことがないか、デフォルト値に設定されただけの天気を区別できないことを意味します。


問題は、値が設定されていないことを確認する方法だと思います。nullとは異なり、値が初期化されていることを示しているようです。
RyszardDżegan2013年

値の型は常に設定されているため(少なくとも暗黙的にそれぞれのデフォルト値に設定されているため)、これによってアプローチが無効になることはありません。
Sven Amann 2013年

3

私が使う

public class Foo<T> where T: struct
{
    private T? item;
}

-2
    public class Foo<T>
    {
        private T item;

        public Foo(T item)
        {
            this.item = item;
        }

        public bool IsNull()
        {
            return object.Equals(item, null);
        }
    }

    var fooStruct = new Foo<int?>(3);
        var b = fooStruct.IsNull();

        var fooStruct1 = new Foo<int>(3);
        b = fooStruct1.IsNull();

        var fooStruct2 = new Foo<int?>(null);
        b = fooStruct2.IsNull();

        var fooStruct3 = new Foo<string>("qqq");
        b = fooStruct3.IsNull();

        var fooStruct4 = new Foo<string>(null);
        b = fooStruct4.IsNull();

このタイピングにより、新しいFoo <int>(42)とIsNull()がfalseを返します。これは、意味的には正しいものの、特に意味はありません。
RJ Lohan 2013年

1
42は「人生、宇宙、そしてすべての究極の質問への答え」です。簡単に言うと、すべてのint値のIsNullはfalseを返します(値が0の場合でも)。
RyszardDżegan2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.