非空类型

14

有没有办法在C#中创建非空类型(例如DateTime或TimeSpan)?

此外,是否有一种方法(可能是属性),可以强制执行不为空的参数不会传递给方法和属性,而无需添加 null 检查?

if(arg1 == null)
{
   throw new ArgumentNullException("this attribute is null")
}
9个回答

16

DateTimeTimeSpan是不可为空的,因为它们是struct而不是class

至于您的第二个问题,在C#中没有标准方法可以实现此功能。您可以使用PostSharp这个AOP框架,或者使用Spec#,这是一种全新的语言(C#的扩展),允许实现一些所需的行为。


5
如果你正在使用构建后步骤,那么有一种标准方法可以实现:Code Contracts 官方成为 .NET 4 的一部分(也可单独为 .NET 3.5 下载)。 - Sam Harwell

11

在.NET 4.0 / C# 4.0中,通过代码合同实现的空值检查将变得更加容易,这几乎可以满足您的需求。

结构体已经是非空的,但不要疯狂创建自己的结构体 - 您很少需要它们(类是更常见的)。没有真正的“非空类”概念; 人们已经提出了语法更改,例如:

void Foo(string! arg1) {...}

这将使编译器对arg1进行非空检查-但实际上,代码合同会做更多的工作。在PostSharp中有一些你可以做的事情,但这可能不值得麻烦。

关于非空类的另一个想法(也是它们未被实现的原因之一); 对于非空类,default(T)将是什么?;-p 规范要求default(T)定义明确...


新语法提案和代码契约的区别在于前者可以静态地检查。 - Konrad Rudolph
对于现在正在阅读此内容的人,Code Contracts 现在实现了静态检查。 - Nick Udell
string! 在 Spec# 中。 - nawfal

6
非空类型是值类型,也就是结构体。结构体不能为 null,所以一个例子是:
public struct MyStruct {}

除非参数的类型是ValueType,否则没有内置的方法来确保不会将null作为参数传递给方法。我见过有人创建扩展方法来进行更简单(即更少代码)的断言,以确定参数是否为空,这可能是您的选择。另一方面,检查本身就很简短;并且检查的意图非常清楚。如果使用自定义检查方法,则情况可能并非如此。
C# 4.0将添加更好的选项来通过契约进行编程,但目前还不可用。正如另一个答案所指出的PostSharp是实现你想要的功能的一种选择。PostSharp通过添加后编译步骤来添加额外的代码。
然而,还有一些选项可以静态地检查是否可能传递null。例如,ReSharper允许您使用[NotNull]属性装饰自己的方法参数,并且如果它能确定参数可能为空,则ReSharper将在编译时发出警告。当然,这只是警告您可能存在(潜在的)不良编码实践,它不是运行时检查,不应该作为这样使用。

4
你说得对:相比于C++,这是C#的一个缺点。这很遗憾,因为我传递给函数的95%参数都是非空指针。在C++中,你可以添加编译器检查的文档,指示哪些指针确保指向某个东西。

2

1

是的!C# 8.0引入了非空引用类型: https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references

string message = null;

// warning: dereference null.
Console.WriteLine($"The length of the message is {message.Length}");

var originalMessage = message;
message = "Hello, World!";

// No warning. Analysis determined "message" is not null.
Console.WriteLine($"The length of the message is {message.Length}");

// warning!
Console.WriteLine(originalMessage.Length);

这在新项目中默认启用。如果您更新现有项目的 .NET 版本,则不会自动启用,但您可以在 项目 -> 属性 -> 可空性 中启用它。


1
关于第二个问题,这里有一个受到Nullable启发的想法。
带有空值检查参数的方法应该长这样:
void Foo(NotNull<string> s)
{
    var x = $"{s}";
    var i = int.Parse(s);
}

NotNull<T> 的用法不仅限于方法参数。如果语言在未来有一些语法糖,例如 Foo(string! s),那肯定会很好。

public struct NotNull<T> where T : class
{
    private T valueField;

    public NotNull(T value)
    {
        this.valueField = value;
        this.CheckNotNull(value);
    }

    public T Value => this.valueField;

    public static implicit operator T(NotNull<T> t)
    {
        return t.Value;
    }

    public static implicit operator NotNull<T>(T t)
    {
        return new NotNull<T>(t);
    }

    public override bool Equals(object other)
    {
        return this.Value.Equals(other);
    }

    public override int GetHashCode()
    {
        return this.Value.GetHashCode();
    }

    public override string ToString()
    {
        return this.Value.ToString();
    }

    private void CheckNotNull(T value)
    {
        if (value == null)
        {
            throw new InvalidOperationException($"Value cannot be null");
        }
    }
}

1

结构体(值类型)变量永远不会为null - 这就解释了你的DateTime案例。因此,如果你的方法参数是C#结构体,你可以确定它们永远不会为null。
然而,如果你的方法参数是引用类型,它们可能为null。在这种情况下,我认为你不能像上面展示的那样摆脱null检查。


0

当然,您可以编写自己的值类型(enumstruct),这些类型不能为null(除非可空)。

至于第二部分,您可以有一个泛型参数和一个只接受值类型的约束条件,这意味着参数不能为空 - 这在我们使用class的绝大多数情况下并不是很有用。

public static void Do<T>(T arg1) where T : struct
{
    //both struct and enum goes here.
}

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