C# 当用作泛型时,无法将可为null的枚举类型分配给null

C# 当用作泛型时,无法将可为null的枚举类型分配给null,c#,nullable,C#,Nullable,我在使用C 9时遇到了这个奇怪的问题,所以我在下面写了一个简单的例子来演示它。我需要将可为null的枚举的值设置为null,但通过泛型类型执行此操作时会出错。如果我将枚举类型硬编码到类中,它工作得很好,因此我不确定当同一类型用作泛型时它为什么不工作。好像是托普顿?被视为Topion并忽略可为null的部分,这将使此错误有意义,因为它将尝试将null分配给不可为null的值类型 这是可空类型、值类型和编译器假设之间的奇怪冲突吗?难道你不应该这样做吗?被视为完全相同的选项?选项何时用作泛型类型 请注

我在使用C 9时遇到了这个奇怪的问题,所以我在下面写了一个简单的例子来演示它。我需要将可为null的枚举的值设置为null,但通过泛型类型执行此操作时会出错。如果我将枚举类型硬编码到类中,它工作得很好,因此我不确定当同一类型用作泛型时它为什么不工作。好像是托普顿?被视为Topion并忽略可为null的部分,这将使此错误有意义,因为它将尝试将null分配给不可为null的值类型

这是可空类型、值类型和编译器假设之间的奇怪冲突吗?难道你不应该这样做吗?被视为完全相同的选项?选项何时用作泛型类型

请注意,在解决这个问题的实际案例中,我不能将TOption约束为值类型,我认为这个约束是不必要的。我不需要TOption是一个值类型,我只需要该字段可以为null——不管它是类还是结构

关于卖出期权?在for TOption中,我仍然需要将其视为不可为null的字段。所以我不能这样做,我需要泛型中的实际类型,但我需要能够区分该类型的不可为null和可为null的字段-独立于作为结构或类的类型。我应该指出,我使用的是可为null的引用类型,因此类被视为不可为null,除非用?指定

公共课程 { 公共静态无效字符串[]args { var测试=新测试; test.Option1=null; test.Option2=null;//无法将null转换为“Program.Option”,因为它是不可为null的值类型 } 公共枚举选项{A,B,C} 公开课考试 { 公共选项选项0{get;set;} 公共选项?选项1{get;set;} 公共选项?选项2{get;set;} } } Nullable要求T为值类型,请参见,但编译器无法从泛型类型派生此值类型。因此,您需要帮助他使用约束:

public class Test<TOption> where TOption : struct
{
    public Option? Option1 { get; set; }
    public TOption? Option2 { get; set; }
}
Nullable要求T为值类型,请参见,但编译器无法从泛型类型派生此值类型。因此,您需要帮助他使用约束:

public class Test<TOption> where TOption : struct
{
    public Option? Option1 { get; set; }
    public TOption? Option2 { get; set; }
}

在不限制struct或使用default的情况下,您可以最接近您想要的类型,以便在泛型参数中提供可能适合您或可能不适合您的可空类型

var test = new Test<Option?>
{
    Option1 = null,
    Option2 = null // works
};

Console.WriteLine(test.Option2.HasValue);
这方面的问题是,泛型类仍然不知道它是在内部约束到结构还是类,这可能仍然会以各种方式限制您,具体取决于这里的用例


因此,根据您更新的需求,如果您不能使用可为null的类型;您需要一个可为空的泛型实例属性;你不能约束到一个结构,那么你可能需要重新思考你的问题。CLR无法在编译时计算出您提供的泛型参数可以为Null,因此将产生编译器错误。

在不限制struct或使用default的情况下,您可以获得最接近所需的值,从而在泛型参数中提供可能适合您或可能不适合您的Null类型

var test = new Test<Option?>
{
    Option1 = null,
    Option2 = null // works
};

Console.WriteLine(test.Option2.HasValue);
这方面的问题是,泛型类仍然不知道它是在内部约束到结构还是类,这可能仍然会以各种方式限制您,具体取决于这里的用例


因此,根据您更新的需求,如果您不能使用可为null的类型;您需要一个可为空的泛型实例属性;你不能约束到一个结构,那么你可能需要重新思考你的问题。CLR无法在编译时计算出您提供的泛型参数可以为null,因此将产生编译器错误。

为什么不这样做

   public class Program
    {
        public enum EnumOption { A, B, C }
        public class ClassOption { public int A { get; set; } }
        public interface InterfaceOption { public int A { get; set; } }
        public struct StructOption { int A; }

        public class Test<TOption>
        {
            public TOption GenericOption { get; set; }
        }
        

       // main entry
        public static void Main(string[] args)
        {
            // reference type  type 
            new Test<ClassOption>().GenericOption = null;
            new Test<InterfaceOption>().GenericOption = null;
            // nullable value type
            new Test<StructOption?>().GenericOption = null;
            new Test<EnumOption?>().GenericOption = null;
            new Test<int?>().GenericOption = null;

            // non nullable value type, use default for init/comparison
            new Test<StructOption?>().GenericOption = default;
            new Test<EnumOption?>().GenericOption = default;
            new Test<int?>().GenericOption = default;
        }

    }

为什么不像这样

   public class Program
    {
        public enum EnumOption { A, B, C }
        public class ClassOption { public int A { get; set; } }
        public interface InterfaceOption { public int A { get; set; } }
        public struct StructOption { int A; }

        public class Test<TOption>
        {
            public TOption GenericOption { get; set; }
        }
        

       // main entry
        public static void Main(string[] args)
        {
            // reference type  type 
            new Test<ClassOption>().GenericOption = null;
            new Test<InterfaceOption>().GenericOption = null;
            // nullable value type
            new Test<StructOption?>().GenericOption = null;
            new Test<EnumOption?>().GenericOption = null;
            new Test<int?>().GenericOption = null;

            // non nullable value type, use default for init/comparison
            new Test<StructOption?>().GenericOption = default;
            new Test<EnumOption?>().GenericOption = default;
            new Test<int?>().GenericOption = default;
        }

    }

失败的原因与我给出的解释非常相似。虽然在这种情况下有一个解决方法,但在您的情况下,您实际上需要一个可为null的值类型,这使得这变得非常不可能

T?对于CLR来说,这意味着两种截然不同的东西,这取决于T是什么。如果T是值类型,则表示可为null。如果T是引用类型,T?实际上,就CLR而言,它与T在某些属性上是相同的

那么,当编译器编译您的代码时,它会说Option2属于哪种类型?换句话说,如果您使用反射检查typeofTest note的成员打开类型,Option2属性的类型是什么

在理想情况下,如果Topion是值类型,则希望Option2的类型为Nullable;如果Topion是引用类型,则希望Option2的类型为Topion。但是如果我们要检查typeofTest属性的类型,我们会得到哪种类型?不可能两者都有,不是吗

实际上,编译器选择TOption作为Option2的类型,并将Option2的类型视为可为null,但也要记住它也可以是不可为null的值类型

这就是为什么这是不可能的 只需说出T?即可获得可为空的值和引用类型

一个相当难看的解决方法是创建自己的Nullable,它不限制值类型:

struct MyNullable<T> where T: notnull {
    private T value;
    
    public bool HasValue {
        get;
    }
    
    public T Value { 
        get {
            if (HasValue) {
                return value;
            } else {
                throw new InvalidOperationException("Value is not present!");
            }
        } 
    }
    
    public MyNullable(T t) {
        value = t;
        HasValue = t != null;
    }
}
现在您可以执行以下操作:

public class Test<TOption>
{
    public Option Option0 { get; set; }
    public Option? Option1 { get; set; }
    public MyNullable<TOption> Option2 { get; set; }
}

失败的原因与我给出的解释非常相似。虽然在这种情况下有一个解决方法,但在您的情况下,您实际上需要一个可为null的值类型,这使得这变得非常不可能

T?对于CLR来说,这意味着两种截然不同的东西,这取决于T是什么。如果T是值类型,则表示可为null。如果T是引用类型,T?实际上,就CLR而言,它与T在某些属性上是相同的

那么,当编译器编译您的代码时,它会说Option2属于哪种类型?换句话说,如果您使用反射检查typeofTest note的成员打开类型,Option2属性的类型是什么

在理想情况下,如果Topion是值类型,则希望Option2的类型为Nullable;如果Topion是引用类型,则希望Option2的类型为Topion。但是如果我们要检查typeofTest属性的类型,我们会得到哪种类型?不可能两者都有,不是吗

实际上,编译器选择TOption作为Option2的类型,并将Option2的类型视为可为null,但也要记住它也可以是不可为null的值类型

这就是为什么仅仅说T?不可能实现可空值和引用类型的原因

一个相当难看的解决方法是创建自己的Nullable,它不限制值类型:

struct MyNullable<T> where T: notnull {
    private T value;
    
    public bool HasValue {
        get;
    }
    
    public T Value { 
        get {
            if (HasValue) {
                return value;
            } else {
                throw new InvalidOperationException("Value is not present!");
            }
        } 
    }
    
    public MyNullable(T t) {
        value = t;
        HasValue = t != null;
    }
}
现在您可以执行以下操作:

public class Test<TOption>
{
    public Option Option0 { get; set; }
    public Option? Option1 { get; set; }
    public MyNullable<TOption> Option2 { get; set; }
}

可空值类型和引用类型是非常不同的类型。无论类型是值还是引用类型,都不能表示为nullable。我不认为有必要使用此约束-您的想法和规范所述是两种不同的东西。为了明确这一点,我建议您按照给定的答案中的建议修改测试。constrained to struct,并创建一个约束到类的Test2。编译两者,然后使用ILDASM反编译。你会发现两者的代码是不同的。@Damien_不相信者如果我想要的东西在当前编译器中是不可能的,那没关系,我只是想确定我想要的东西在理论上是可能的。@Cains这就是为什么所有人都回答并评论了你的枚举问题,乍一看。。。因为标题、代码和暴露的问题。你已经告诉所有人这不合适。大量的评论意味着我不能再投票了。于是我删除了我的答案,投了00110001的赞成票。请看一看,可空值和引用类型是非常不同的类型。无论类型是值还是引用类型,都不能表示为nullable。我不认为有必要使用此约束-您的想法和规范所述是两种不同的东西。为了明确这一点,我建议您按照给定的答案中的建议修改测试。constrained to struct,并创建一个约束到类的Test2。编译两者,然后使用ILDASM反编译。你会发现两者的代码是不同的。@Damien_不相信者如果我想要的东西在当前编译器中是不可能的,那没关系,我只是想确定我想要的东西在理论上是可能的。@Cains这就是为什么所有人都回答并评论了你的枚举问题,乍一看。。。因为标题、代码和暴露的问题。你已经告诉所有人这不合适。大量的评论意味着我不能再投票了。于是我删除了我的答案,投了00110001的赞成票。请看一看。我在发帖后很快就注意到了这一点,并做了一个说明。TOption可能不是一个结构,我也不需要它,在这个例子中它正好是一个。我需要的是可空性,而不是一个定值类型。如果TOption是一个引用类型,那么可空性意味着什么?我想?那么,仅仅是语法上的糖吗?理想情况下,我希望如果它是一个引用类型,它只是一个类型,因为它已经可以为null。实际上,我在这里要做的是使字段可以为Null—引用类型已经可以为Null,而值类型将包含在nullable中。请参阅:public struct nullable,其中T:struct:so nullable仅适用于值类型。我在上面做了一个编辑,以解决您的新建议。我认为由于语言/编译器的限制,这可能是不可能的。这里的一个重要细节是,我也在使用可为空的引用类型,所以我不确定是否使用了?对于Nullable来说,这必然是一个直接的下降。我在发布后很快就注意到了这一点,并做了一个说明。TOption可能不是一个结构,我也不需要它,在这个例子中它正好是一个。我需要的是可空性,而不是一个定值类型。如果TOption是一个引用类型,那么可空性意味着什么?我想?那么,仅仅是语法上的糖吗?理想情况下,我希望如果它是引用类型,那么
只需将该类型设置为可为null的类型即可。实际上,我在这里要做的是使字段可以为Null—引用类型已经可以为Null,而值类型将包含在nullable中。请参阅:public struct nullable,其中T:struct:so nullable仅适用于值类型。我在上面做了一个编辑,以解决您的新建议。我认为由于语言/编译器的限制,这可能是不可能的。这里的一个重要细节是,我也在使用可为空的引用类型,所以我不确定是否使用了?必须是可空字段的直接输入。不幸的是,这不适合我,因为我还需要该泛型的不可空字段,我在我的问题中对此添加了更多详细信息。不幸的是,这不适合我,因为我还需要该泛型的不可空字段,我在我的问题中添加了更多细节。@.Sweeper什么是notnull以及notnull如何可以在此处为null:t!=无效的与@.00110001答案相比,这是如何解决OP问题的?似乎我在理想世界中想要的东西不会在CLR中实现,尽管C语言中增加了所有新的可空性。我将看看您使用自定义的可空类型是否适用于我的情况,否则我将不得不重新设计并牢记此限制。@OlivierRogier请参阅中的解释。我把它放在那里只是为了防止像MyNullable这样的嵌套nullable。@。Sweeper我不能签入VS2017,但我不明白not nullable如何可以为null并解决OP问题。@Cains注意到内置nullable有很多语法糖分和取消装箱优化,这是您自己的nullable所没有的,这就是为什么这是一个难看的解决方法。@.Sweeper什么是notnull以及notnull如何可以在此处为null:t!=无效的与@.00110001答案相比,这是如何解决OP问题的?似乎我在理想世界中想要的东西不会在CLR中实现,尽管C语言中增加了所有新的可空性。我将看看您使用自定义的可空类型是否适用于我的情况,否则我将不得不重新设计并牢记此限制。@OlivierRogier请参阅中的解释。我把它放在那里只是为了防止像MyNullable这样的嵌套nullable。@。Sweeper我不能签入VS2017,但我不明白not nullable如何可以为null并解决OP问题。@Cains注意到内置nullable有很多语法糖分和取消装箱优化,这是您自己的nullable所没有的,这就是为什么这是一个丑陋的解决办法。