Подтвердить что ты не робот

Ограничение общего на вещи, которые могут быть нулевыми

Я хотел бы ограничить общий код, который я кодирую, для всего, что может быть null. Это в основном любой класс + System.Nullable (например, int? и т.д.).

Для части класса это довольно просто:

public class MyGeneric<T> where T : class {}

Но тогда это не позволяет мне это сделать:

var myGeneric = new MyGeneric<int?>();

или это:

var myGeneric = new MyGeneric<Nullable<int>>();

Компилятор жалуется:
ошибка CS0452: тип 'int?' должен быть ссылочным типом, чтобы использовать его в качестве параметра "T" в родовом типе или методе "Test.MyGeneric"

Итак, я попробовал addind System.Nullable как принятые типы для T:

public class MyGeneric<T> where T : class, System.Nullable {}

Но этого не будет. Компилятор возвращает следующую ошибку:
ошибка CS0717: "System.Nullable": статические классы не могут использоваться в качестве ограничений

Затем я попробовал

public class MyGeneric<T> where T : class, INullable {}

Он компилируется, но затем, когда я делаю:

var myGeneric = new MyGeneric<string>();

Компилятор возвращает эту ошибку:
error CS0311: Тип "string" не может использоваться как параметр типа "T" в родовом типе или методе "Test.MyGeneric". Нет никакого неявного преобразования ссылок из 'string' в 'System.Data.SqlTypes.INullable'.

Итак, вопрос в том, можно ли вообще ограничить родовое для всего, что может быть null, и так, как?

Для справки, я использую VS2010/С# 4.0

изменить
Меня спросили, что я хочу с этим делать. Вот пример:

namespace Test
{
    public class MyGeneric<T> where T : class
    {
        private IEnumerable<T> Vals { get; set; }

        public MyGeneric(params T[] vals)
        {
            Vals = (IEnumerable<T>)vals;
        }

        public void Print()
        {
            foreach (var v in Vals.Where(v => v != default(T)))
            {
                Trace.Write(v.ToString());
            }
            Trace.WriteLine(string.Empty);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyGeneric<string> foo = 
                new MyGeneric<string>("a", "b", "c", null, null, "g");
            foo.Print();
        }
    }
}

Эта программа печатает abcg в консоли отладки.

4b9b3361

Ответ 1

Нет, нет способа сделать это во время компиляции.

Лично я просто хотел бы T быть любым, тогда я бы проверял его достоверность в статическом конструкторе:

public class MyGeneric<T>
{
    static MyGeneric()
    {
        var def = default(T); 
        if (def is ValueType && Nullable.GetUnderlyingType(typeof(T)) == null)
        {
            throw new InvalidOperationException(
                string.Format("Cannot instantiate with non-nullable type: {0}",
                    typeof(T)));
        }
    }
}

Ответ 2

Нет, вы не можете ограничить общий доступ только тем, что может быть нулевым. См. Как я могу вернуть NULL из общего метода в С#?. Единственными представленными решениями были либо использование default(T), либо использование ограничения class, потому что нет возможности ограничить только типы с возможностью NULL.

Не понятно, какова ваша цель. Изменение только пары строк вашего примера кода делает работу с любым типом, а не только с нулевыми значениями, поэтому я не понимаю, почему вы пытаетесь его ограничить.

В этом примере также будут работать с MyGeneric<int?> или MyGeneric<int>:

using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace Test
{
    public class MyGeneric<T> // removed "where T : class"
    {
        public void Print(params T[] vals)
        {
            Print((IEnumerable<T>) vals);
        }

        public void Print(IEnumerable<T> vals)
        {
            foreach (var v in vals.OfType<T>()) // use "OfType" instead of "Where"
            {
                Trace.WriteLine(v.ToString());
            }
            Trace.WriteLine(string.Empty);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyGeneric<string> foo = new MyGeneric<string>();
            foo.Print("a", "b", "c", null, null, "g");
        }
    }
}

Ответ 3

Единственное, что непонятно из вашего примера, - это то, почему вы хотите сделать это с помощью родового уровня класса, а не общего уровня метода. На основе вашего примера вы можете сделать следующее:

public class NonGenericClass
{
    public void Print<T>(IEnumerable<T?> vals) where T : struct
    {
        PrintRestricted(vals.Where(v =>  v.HasValue));
    }

    public void Print<U>(IEnumerable<T> vals) where T : class
    {
        PrintRestricted(vals.Where(v => v != default(T)));
    }

    private void PrintRestricted<U>(IEnumerable<T> vals)
    {
        foreach (var v in vals)
        {
            Trace.WriteLine(v.ToString());
        }
        Trace.WriteLine(string.Empty);
    }
}

За счет написания метода упаковки, который делает ограничение, вы можете получить ту же функциональность.

Ответ 4

Иногда система типа просто не может делать то, что вы хотите. В этих случаях вы либо меняете то, что хотите, либо работаете вокруг.

Рассмотрим пример класса Tuple<>. "Самый большой" вариант выглядит как Tuple<T1, T2, T3, T4, T5, T6, T7, TRest>, где TRest должен быть Tuple<>. Это не ограничение времени компиляции, это строго проверка времени выполнения. Возможно, вам придётся прибегнуть к чему-то подобному, если вы хотите применить требование nullability для T в Foo<T>, поддерживая как типичные классы, так и nullable structs.

/// <summary>
/// Class Foo requires T to be type that can be null. E.g., a class or a Nullable&lt;T&gt;
/// </summary>
/// <typeparam name="T"></typeparam>
class Foo<T>
{
    public Foo()
    {
        if (default(T) != null)
            throw new InvalidOperationException(string.Format("Type {0} is not valid", typeof(T)));
    }

    // other members here
}

При этом я документирую, что класс будет требовать, чтобы T была совместима с типами NULL и выбрала конструктор, если это не так.

Foo<string> foo = new Foo<string>(); // OK
Foo<int?> works = new Foo<int?>(); // also OK
Foo<int> broken = new Foo<int>(); // not OK