web-dev-qa-db-ja.com

nullを許可するすべてのC#ジェネリック型制約

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

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

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

}

現在、すべてをnullにできる型パラメーターとして使用できる型制約を探しています。つまり、すべての参照型とすべてのNullableT?)型を意味します。

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

可能になるはずです。

型制約としてclassを使用すると、参照型のみを使用できます。

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

92
user2963836

コンパイル時のチェックを行うのではなく、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());
21
Matthew Watson

ジェネリックでまたはと同等の実装方法がわかりません。ただし、デフォルト key Wordを使用して、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
    }
}
17
Ryszard Dżegan

「nullable」(参照型またはNullable)のいずれかを取ることができる一般的な静的メソッドが必要な簡単なケースでこの問題に遭遇しました。だから私は、単にTを取り、制約where T : classを取り、別のメソッドがT?を取り、where T : structを持つ2つのオーバーロードメソッドを持つことで、OPの述べられた質問よりも比較的簡単に解決できる独自のソリューションを思い付きました。

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

    //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つは、where TFoo: new()のような型制約の汎用型引数としてFoo<T>を使用できないことです。最後に、ここで必要な余分なコードが少しあります。これは、特に複数のオーバーロードされたコンストラクタが必要な場合に増加します。

11
Dave M

前述のように、コンパイル時のチェックはできません。 .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);
    }
}
7
Aidiakapi

私が使う

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

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

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

代わりに使用することもできます

object.Equals(value, default(T))

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

3
Sven Amann