【问题标题】:c# 8 nullability - generic class "knowing" its nullability [duplicate]c#8 可空性 - 泛型类“知道”它的可空性 [重复]
【发布时间】:2020-06-18 21:34:04
【问题描述】:

我有两个类——一个内部类和一个外部类,其中内部类可以返回一个空值和一个不应该返回空值的外部类(当然,假设类型参数 T 代表一个非可空类型):

#nullable enable
public class Insider<T>
{
    [MaybeNull, AllowNull]
    public T Value {get; set; }

    public Insider()
    { Value = default; }
}
public class Outsider<T>
{
    Insider<T> Inside = new Insider<T>();

    public T Value
    {
        get
        {
            return Inside.Value; // Compiler warning "'Value' may be null here"
        }
        set { Inside.Value = value; }
    }
}
public class Fred
{ }
public static class Test
{
    public static void Stuff()
    {
        Outsider<Fred> one = new Outsider<Fred>();
        Debug.WriteLine("one.Value is " + one.Value);

        Outsider<Fred?> two = new Outsider<Fred?>();
        Debug.WriteLine("two.Value is " + two.Value);

        Outsider<int> three = new Outsider<int>();
        Debug.WriteLine("three.Value is " + three.Value);
    }
}
#nullable disable

当我运行它时,它会给出预期的输出:

one.Value is
two.Value is
three.Value is 0

导致“Insider.Value”为空的程序状态是一种特定的错误状态,调用者应该知道系统处于该状态而不访问“Outsider.Value”,这意味着调用者永远不会看到“ Outsider.Value" 返回 null,但我想强制执行。

是的,两个类都应该处理结构和类。

我当时的想法是这样的:

public class Outsider<T>
{
    Insider<T> Inside = new Insider<T>();

    public T Value
    {
        get
        {
            if ((Inside.Value == null)&&(T_shouldnt_be_null))
                throw new Exception("Nope");
            return Inside.Value;
        }
        set { Inside.Value = value; }
    }
}

当然,麻烦的部分是“T_shouldnt_be_null”。

如果我在 Outsider.Value 中进行更多调试:

public class Outsider<T>
{
    Insider<T> Inside = new Insider<T>();

    public T Value
    {
        get
        {
            Type? nullableType = Nullable.GetUnderlyingType(typeof(T));
            Debug.WriteLine("      typeof(T) is "  + typeof(T) + "; nullableType=" + ((nullableType==null)?"null": nullableType.ToString()));
            foreach (var x in typeof(T).CustomAttributes)
                Debug.WriteLine("         " + x);
            return Inside.Value;
        }
        set { Inside.Value = value; }
    }
}

我得到的结果不是我所期望的。当类型参数 T 为 Fred(不可为空)时的结果是:

typeof(T) is BTSDataCollator.Fred; nullableType=null

当type参数是Fred时的结果呢? (可为空)是:

typeof(T) is BTSDataCollator.Fred; nullableType=null

编译器已经从类型中完全去除了可空性指示符。在这两种情况下,类类型都没有 CustomAttributes(这就是 this post 用来确定可空性的依据)

(通用)类有什么方法可以获取有关其实际类型参数的可空性的信息(Fred 与 Fred?)?

【问题讨论】:

  • Nullable.GetUnderlyingType 用于可空值类型。
  • 您不能在编译时确保局外人的 T 不可为空吗? “公共类局外人 where T: struct”。在这种情况下,如果您尝试使用引用类型(可为空),则会出现编译器错误。
  • 唉,正如我在帖子中所说,“两个类都应该处理结构和类。”
  • @GuruStron 我正在尝试一切我能想到的“看到”可空性。我预计当我实例化 Outsider 时,T 会是 Nullable,但事实并非如此。
  • @PavelAnikhouski 唉,我已经打印出 typeof(T) 的 CustomAttributes 以及 Outsider 的 Value 属性的 CustomAttributes 和 Outsider 的 Value 属性的 PropertyType 的 CustomAttributes。在每种情况下都有 -zero- CustomAttributes,这是 StackOverflow 帖子所依赖的

标签: c# reflection nullable c#-8.0 nullable-reference-types


【解决方案1】:

我会说,在一般情况下,您尝试做的事情是不可能的。如果您检查sharplab.io 中的代码,您会看到没有向泛型类添加任何信息,因此new Outsider&lt;Fred&gt;()new Outsider&lt;Fred?&gt;() 对于编译器来说是完全相同的:

public class C {
    public void M() {
        Outsider<Fred> one = new Outsider<Fred>();
        Outsider<Fred?> two = new Outsider<Fred?>();
    }
}

下一个 IL 的结果:

.method public hidebysig 
    instance void M () cil managed 
{
    // Method begins at RVA 0x2094
    // Code size 14 (0xe)
    .maxstack 1
    .locals init (
        [0] class Outsider`1<class Fred> one,
        [1] class Outsider`1<class Fred> two
    )

    IL_0000: nop
    IL_0001: newobj instance void class Outsider`1<class Fred>::.ctor()
    IL_0006: stloc.0
    IL_0007: newobj instance void class Outsider`1<class Fred>::.ctor()
    IL_000c: stloc.1
    IL_000d: ret
} // end of method C::M

所以基本上没有可以通过反射获得的信息。

【讨论】:

  • 啊。这是很好的分析。不过我想知道......如果我在一个库中创建两个类“class One:Outsider”和“class Two:Outsider”,然后在该库之外使用这些类 - 这不意味着“一”和“二”的签名必须以某种方式表明它们的可空性?我不熟悉 C# 世界中的库,所以我不确定它是如何工作的
  • 是的!类一具有自定义属性 [System.Runtime.CompilerServices.NullableAttribute(new Byte[2] { 0, 1 })] 而类二具有自定义属性 [System.Runtime.CompilerServices.NullableAttribute(new Byte[2] { 0 , 2 })]。您甚至不必在不同的库中看到它,只需创建新的子类。不知道这有什么帮助......但是......
  • 哦...这将是一些非常邪恶的代码...我想知道如果我使用表达式树定义一个子类然后编译它会发生什么...
  • @BettyCrokker 恐怕你需要使用Reflection.Emit 之类的东西,不确定你是否可以用表达式树声明类。
  • 是的,即使这样也是不行的。我可以使用从 Outsider 派生的 AssemblyBuilder 和 TypeBuilder 创建一个类,但是您将基类指定为 Type,并且 Outsider 和 Outsider 的类型相同(甚至可以使用相同的 GUID)
猜你喜欢
  • 2022-07-07
  • 1970-01-01
  • 2020-03-20
  • 1970-01-01
  • 2021-02-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多