C#泛型类使用引用类型和可空值类型

3

我有一个有趣的问题。我想创建一个通用类,既可以处理引用类型,也可以处理Nullable<T>类型。基本上我想要这样的东西:

public class ClassWithNull<T>
{
    public T varName = null;
}

现在这段代码当然不能编译,因为不是所有类型都可以赋值为null,即非可空值类型。但问题在于Nullable<T>是值类型,所以仅添加where T:class无法解决问题。我的泛型知识不太牢固,但我找不到任何方法来表达T必须是引用类型或可空值类型之一的要求。
我想解决这个问题的方法是将ClassWithNull<T>变为抽象类。然后,我可以添加两个子类,一个用于处理引用类型,另一个用于处理可空值类型。然后,在基类中使用静态工厂方法并通过反射来确定应该构建哪个子类。类似以下内容:
public static ClassWithNull<T> CreateClassWithNull<T>()
{
    StackTrace st = new StackTrace();
    Type type = st.GetFrame(1).GetMethod().GetGenericArguments()[0];
    if (!type.IsValueType)
    {
        return new ClassWithReferenceType<T>();
    }
    else if (type == typeof(Nullable))
    {
        return new ClassWithNullableValueType<T>();
    }
    else
    {
        throw new Exception("Must provide nullable type.");
    }
}

这里的问题在于泛型是静态解析的。如果 ClassWithReferenceType<U> 期望 U 是引用类型,则在工厂方法中调用 new ClassWithReferenceType<T>() 是编译错误,因为并不需要 T 是引用类型。编译器不知道运行时检查。
有没有关于如何实现这样一件事的想法?

请注意,只有当x是可空类型时,T x = null;才会编译通过,但T x = default(T)x == null始终编译通过(对于非可空类型,它只返回false)。 - Gabe
在您的代码中,typeof(Nullable) 引用了一个与您所想不同的类型,即一个非泛型的静态类。可以使用 type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) 来代替。在这种特殊情况下的另一种选择是 Nullable.GetUnderlyingType(type) != null。另外,请注意您可以使用 typeof(T) 而不是您称之为的 type - Jeppe Stig Nielsen
2个回答

5
如何呢:
public class ClassWithNull<T>
{
    public T varName = default(T);
}

实际上,你甚至不需要赋值——你可以在构造时将其保留为默认值。但对于局部变量,你可能希望使用default(T)

这并不能阻止你错误地使用非空值类型——但这已经足够了吗?

如果这对你没有帮助,我建议编写两个静态方法,就像这样:

public static ClassWithNull<T> CreateClassWithNullForClass<T> where T : class
{
    return new ClassWithReferenceType<T>();
}

public static ClassWithNull<T> CreateClassWithNullForNullable<T> where T : struct
{
    return new ClassWithNullableValueType<T>();
}

ClassWithNullableValueType中的字段将是Nullable<T> - T将是基础类型。

现在,如果您想要相同方法的重载,那就有点困难了,特别是如果您不想传递任何参数。这是可能的,但是非常可怕


我认为他对默认值(T)不感兴趣,因为在某些情况下它可能不为空。他的意图是只允许分配空值(“必须是引用类型或可空值类型”)。 - Wiktor Zychla
@Wiktor:default(T) 对于任何具有空值的 T 始终是空值。但是,没有约束条件允许表达这一点。不过,请参见我的其他答案以获取更多选项... - Jon Skeet
你修改后的答案正好包含了我想到的解决方案。 - Wiktor Zychla
是的,第一种解决方案不够好。但第二种方案很棒。我不知道为什么从来没有想过有两个工厂方法。我将在三分钟内接受这个答案(为什么SO防止提问者在10分钟内接受答案?) - jjoelson
@WiktorZychla:好的。如果可能的话,我想我还是会坚持第一种方法 :) - Jon Skeet
@jjoelson: 这是为了让别人有机会添加一个好的答案。第一个“好吧,它起作用”的答案不一定是最好的 - 有时候几分钟后会有人发布一个更好的答案。也许在这种情况下会有人这么做 :) - Jon Skeet

0
你应该能够做到这个:
public class ClassWithNull<T>
{
    private object varName_priv = null;

    public T varName {
        get { return (T)varName_priv; }
        set { varName_priv = value; }
    }
}

这是有效的,因为在C#中,每个非指针类型都可以转换为object,包括值类型。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接