Ограничение общего на вещи, которые могут быть нулевыми - PullRequest
23 голосов
/ 21 апреля 2011

Я хотел бы ограничить общий код, который я кодирую, всем, что может быть 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>();

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

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

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

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

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 в консоли отладки.

Ответы [ 4 ]

12 голосов
/ 21 апреля 2011

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

Лично я бы просто позволил 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 голосов
/ 21 апреля 2011

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

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

Этот пример будет работать с 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");
        }
    }
}
1 голос
/ 21 апреля 2011

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

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

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

0 голосов
/ 21 апреля 2011

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

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

/// <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 был совместим с обнуляемыми типами, и добавил конструктор, если это не так.

...