Можете ли вы определить универсальный тип, который принимает * любой * тип, значение или ссылку, допускающий значение NULL?

Игра с новыми ссылочными типами, допускающими значение NULL, в C #. Рад видеть, что они переманили это у Свифта! Это такая замечательная функция! НО ... поскольку он по сути «привязан» к языку, я изо всех сил пытаюсь создать универсальный тип, который может принимать любой обнуляемый тип, будь то значение или ссылка, что тривиально в Swift.

Рассмотрим этот класс:

public abstract class LabeledValue<TValue> {
    public string? label { get; set; }
    public TValue? value { get; set; }
}

Вот чего я пытаюсь достичь, используя Int и класс под названием 'Foo' в качестве примера:

public class LabeledInt : LabeledValue<Int>{}

var myLabeledIntA = new LabeledInt(){
    label = "Forty Four",
    value = 44
}

var myLabeledIntB = new LabeledInt(){
    label = "Not Set",
    value = null
}

public class LabeledFoo : LabeledValue<Foo>{}

var myLabeledFooA = new LabeledFoo(){
    label = "Set",
    value = new Foo()
}

var myLabeledFooB = new LabeledFoo(){
    label = "Not Set",
    value = null
}

Он жалуется, что мне нужно определить TValue как допускающее значение NULL. Однако я не могу найти ограничение, которое разрешает как типы значений, допускающие значение NULL (например, Int?), Так и ссылочные типы, допускающие значение NULL (например, Foo?). Как написать такое ограничение?

Это не работает ...

public abstract class LabeledValue<TValue>
where TValue : Nullable {
    public string? label { get; set; }
    public TValue? value { get; set; }
}

public abstract class LabeledValue<TValue>
where TValue : struct {
    public string? label { get; set; }
    public TValue? value { get; set; }
}

public abstract class LabeledValue<TValue> {
    public string?          label { get; set; }
    public Nullable<TValue> value { get; set; }
}

Обратите внимание, я также пробовал это, думая, что допускаемое значение NULL можно просто передать как фактический параметр типа, но затем он жалуется, что «Значение» не установлено.

public abstract class LabeledValue<TValue> {
    public string? label { get; set; }
    public TValue  value { get; set; }
}

public class LabeledInt : LabeledValue<Int?>{}

person Mark A. Donohoe    schedule 14.01.2020    source источник
comment
Вы пробовали искать здесь существующие ответы? Nullable<T> - это структура и тип значения, ссылки, допускающие значение NULL, явно предназначены для ссылочных типов. Вы должны использовать общее ограничение class или struct, как это объясняется в разделе issue with T? в этом статья   -  person Pavel Anikhouski    schedule 14.01.2020
comment
Если вы посмотрите выше, я попробовал это (последний ответ во втором и последнем блоке кода). И да, я искал здесь. Когда я не нашел ничего, что отвечало на этот вопрос, я разместил этот вопрос. Если вам известно решение, пожалуйста, опубликуйте его в качестве ответа здесь, и если оно сработает, я отмечу его как таковое.   -  person Mark A. Donohoe    schedule 14.01.2020
comment
Вы можете взглянуть на этот поток хотя бы   -  person Pavel Anikhouski    schedule 14.01.2020
comment
TValue? когда TValue: class и когда TValue: struct - это совершенно разные вещи с точки зрения CLR, обнуляемый ссылочный тип аннотируется с использованием атрибутов, обнуляемый тип значений - это Nullable<T> тип. Вы должны сами обдумать, чего вы хотите в этом случае. Или украсить шрифт атрибутами до и после состояния   -  person Pavel Anikhouski    schedule 14.01.2020
comment
Да, я только что опубликовал ответ по этому поводу. Позор, но я понимаю, поскольку они были привязаны к C #, тогда как они были фундаментальной частью Swift с самого начала. В Swift, независимо от того, является ли T классом или структурой, вариант с нулевым значением - это конкретный Optional<T>, поэтому они одного типа, следовательно, это разрешено.   -  person Mark A. Donohoe    schedule 14.01.2020


Ответы (2)


Хорошо, нашел. Вы должны использовать два новых явных атрибута, AllowNull и MaybeNull.

Вот исправленный код ...

public abstract class LabeledValue<TValue> {

    public string? label { get; set; }

    [AllowNull, MaybeNull]
    public TValue value { get; set; }
}

С этим изменением я теперь могу делать все следующее ...

public class LabeledInt  : LabeledValue<int>{}
public class LabeledNInt : LabeledValue<int?>{}
public class LabeledFoo  : LabeledValue<Foo>{}
public class LabeledNFoo : LabeledValue<Foo?>{}

И используйте их вот так ...

var a = new LabeledInt();
a.Value = 4;
a.value = null // This won't compile

var b = new LabeledNInt();
b.Value = 4;
b.Value = null; // This compiles just fine

var c = new LabeledFoo();
c.Value = new Foo();
c.Value = null; // This won't compile

var d = new LabeledNFoo();
d.Value = new Foo();
d.Value = null; // This compiles just fine

Примечание. По-прежнему отображается предупреждение о том, что Value не инициализирован, но это только предупреждение, а не ошибка. Вы должны убедиться, что явно установили Value для ненулевых типов, прежде чем обращаться к нему. Вид поражения цели использования типов, допускающих значение NULL / не допускающих значение NULL, но это скорее взлом, чем истинное решение, которое на самом деле невозможно, поскольку типы значений, допускающие значение NULL, на самом деле являются конкретными Nullable<T>, тогда как ссылочные типы, допускающие значение NULL, являются обычными ссылочными типами, просто украшенными атрибут, позволяющий компилятору не принимать значения NULL.

person Mark A. Donohoe    schedule 14.01.2020

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

Например, вот команда map для C #, которая эмулирует функцию, которую я регулярно использую в Swift. Поскольку он основан на двух универсальных типах, оба должны принимать значения NULL, мне нужно создать четыре «версии» функции.

// Class-Class version
public static U? Map<T,U>(this T? item, Func<T, U?> formatClosure)
where T : class
where U : class
    => (item != null)
        ? formatClosure(item)
        : null;

// Struct-Struct version
public static U? Map<T,U>(this T? item, Func<T, U?> formatClosure)
where T : struct
where U : struct
    => item.HasValue
        ? formatClosure(item.Value)
        : null;

// Class-Struct version
public static U? Map<T,U>(this T? item, Func<T, U?> formatClosure)
where T : class
where U : struct
    => (item != null)
        ? formatClosure(item)
        : null;

// Struct-Class version
public static U? Map<T,U>(this T? item, Func<T, U?> formatClosure)
where T : struct
where U : class
    => item.HasValue
        ? formatClosure(item.Value)
        : null;

Да, это многословно, но опять же, это необходимо, потому что ссылочный тип, допускающий значение NULL, не то же самое, что тип значения, допускающий значение NULL. Это делает язык прозрачным, поскольку он обрабатывает все варианты.

person Mark A. Donohoe    schedule 02.02.2020