Ограничение типа С# для всех значений NULL

Итак, у меня есть этот класс:

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

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

}

Теперь я ищу ограничение типа, которое позволяет мне использовать все как параметр типа, который может быть null. Это означает, что все ссылочные типы, а также все типы Nullable (T?):

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

должно быть возможно.

Использование class, поскольку ограничение типа позволяет мне использовать ссылочные типы.

Дополнительная информация: Я пишу приложение для труб и фильтров и хочу использовать ссылку null как последний элемент, который переходит в конвейер, чтобы каждый фильтр мог нормально закрываться, выполнять очистку и т.д.

Ответ 1

Если вы хотите сделать проверку времени исполнения в конструкторе Foo, а не проверять время компиляции, вы можете проверить, не является ли тип не ссылочным или нулевым типом, и выдавать исключение, если это дело.

Я понимаю, что только проверка времени выполнения может быть неприемлемой, но на всякий случай:

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());

Ответ 2

Я не знаю, как реализовать эквивалент OR в generics. Однако я могу предложить использовать ключевое слово по умолчанию, чтобы создать значение 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
    }
}

Ответ 3

Такое ограничение типа невозможно. В соответствии с документацией ограничений типов не существует ограничений, которые фиксируют как нулевые, так и ссылочные типы. Поскольку ограничения можно комбинировать только в конъюнкции, невозможно создать такое ограничение по комбинации.

Однако вы можете вернуться к параметру типа unconstraint, поскольку вы всегда можете проверить на == null. Если тип является типом значения, проверка всегда будет оцениваться как false. Тогда вы, возможно, получите предупреждение R # "Возможное сравнение типа значения с нулевым", что не является критическим, если семантика подходит вам.

Альтернативой может быть использование

object.Equals(value, default(T))

вместо нулевой проверки, поскольку значение по умолчанию (T), где T: class всегда равно нулю. Это, однако, означает, что вы не можете отличить погоду, значение, не подлежащее обнулению, никогда не было задано явно или просто установлено на его значение по умолчанию.

Ответ 4

Как уже упоминалось, у вас не может быть проверки времени компиляции. Общие ограничения в .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 a nullable type, this works just fine
        variable = default(T);
    }
}

Ответ 5

Я столкнулся с этой проблемой для более простого случая, когда вам нужен универсальный статический метод, который мог бы взять что-либо "нулевое" (либо ссылочные типы, либо Nullables), что привело меня к этому вопросу без удовлетворительного решения. Поэтому я придумал свое решение, которое было относительно легче решить, чем заданный OP вопрос, просто имея два перегруженных метода, один из которых принимает T и имеет ограничение where T : class, а другое, которое принимает T?, и имеет where T : struct.

Затем я понял, что это решение также может быть применено к этой проблеме для создания решения, которое можно проверить во время компиляции, сделав конструктор закрытым (или защищенным) и используя статический метод factory:

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

В этом решении есть несколько недостатков: вы можете использовать 'new' для создания объектов. Другим является то, что вы не сможете использовать Foo<T> в качестве аргумента общего типа для ограничения типа чего-то вроде: where TFoo: new(). Наконец, это бит дополнительного кода, который вам нужен, что увеличится, особенно если вам нужно несколько перегруженных конструкторов.

Ответ 6

Я использую

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

Ответ 7

    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();