Можете ли вы определить обобщенный c, который принимает * любой * обнуляемый тип, значение или ссылку? - PullRequest
6 голосов
/ 14 января 2020

Игра с новыми обнуляемыми ссылочными типами в C#. Рад видеть, что они взломали это от Свифта! Это такая замечательная особенность! НО ... так как он по сути "привязан" к языку, я изо всех сил пытаюсь создать универсальный 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 (т. Е. Int?), И ссылочные типы Nullable (т. Е. 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; }
}

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

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

public class LabeledInt : LabeledValue<Int?>{}

Ответы [ 2 ]

4 голосов
/ 14 января 2020

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

0 голосов
/ 02 февраля 2020

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

Например, вот команда map для C# которая имитирует функцию, которую я регулярно использую в Swift. Поскольку он опирается на два обобщенных типа c, которые оба должны принимать значения 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;

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

...