如何在C#的getter和setter方法中添加验证?

8
在C#中,我可以拥有一个属性而无需声明私有变量。 我的VB6代码看起来像这样:
'local variable(s) to hold property value(s)
Private mvarPhoneNumber As String 'local copy
Public Property Let PhoneNumber(ByVal vData As String)
'used when assigning a value to the property, on the left side of an assignment.
'Syntax: X.PhoneNumber = 5
    mvarPhoneNumber = vData
End Property


Public Property Get PhoneNumber() As String
'used when retrieving value of a property, on the right side of an assignment.
'Syntax: Debug.Print X.PhoneNumber
    PhoneNumber = mvarPhoneNumber
End Property

现在可以像这样看起来。
public string PhoneNumber{get;set;}

我该如何在C#的getter和setter方法中添加验证?我尝试添加以下验证:

public string PhoneNumber
        {
            get
            {
                return PhoneNumber;
            }
            set
            {
                if (value.Length <= 30)
                {
                    PhoneNumber = value;
                }
                else
                {
                    PhoneNumber = "EXCEEDS LENGTH";
                }
            }
        }

这段代码的get部分无法编译。 我需要回归使用私有变量吗?


正如Andrew所说,你需要为属性创建一个私有变量。C#的自动属性功能实际上会在幕后为您执行此操作,但您无法通过该方式访问该私有变量。您必须显式声明它以执行任何与其相关的操作(例如验证)。 - Chris
1
另外,请确保在setter中设置后备字段,否则您将在运行时遇到StackOverflowException。 - Mark Avenius
我不知道你是否只是为了举例而简化,但是盲目地将属性设置为新值而不抛出ArgumentException似乎相当危险。 - Jon Hanna
1
我稍微偏离话题,回应一下你的开场白。在VB6中,与C#自动属性public string PhoneNumber{get;set;}相当的是一个简单的公共字段,Public PhoneNumber As String。在VB6中,您可以随意将公共字段替换为属性,而不会影响任何功能:调用者无需重新编译,数据绑定仍然有效...这是VB6比.Net更好的领域之一。 - MarkJ
5个回答

18

是的,你将不得不创建一个后备字段:

string _phoneNumber;

public string PhoneNumber
{
    get
    {
        return _phoneNumber;
    }
    set
    {
        if (value.Length <= 30)
        {
            _phoneNumber = value;
        }
        else 
        {
            _phoneNumber = "EXCEEDS LENGTH";
        }
    }
}

请记住,这个实现和自动实现属性没有任何区别。当你使用自动实现属性时,你只是允许编译器为你创建后备字段。如果你想要在 getset 中添加任何自定义逻辑,你必须像我上面展示的那样手动创建字段。


谢谢。我不确定是否需要。 - abhi

2

理论上说,你并不一定需要一个本地变量。在get/set属性中,你可以实现任何想要的功能。但是,在你的例子中,你对get/set属性进行了递归访问,这种实现方式没有意义。因此,在你具体的情况下,你需要一个本地变量,没错。


2
我会这样做以避免NullReferenceException的发生,并缩短整体代码。
public string PhoneNumber
{
    get { return _phoneNumber; }
    set 
    {
        var v = value ?? string.Empty; 
        _phoneNumber = v.Length <= 30 ? v : "EXCEEDS LENGTH"; 
    }
}
private string _phoneNumber;

最好在getter中处理null值,而不是在setter中处理。如果在属性初始化之前访问getter会怎样呢?我会将get更改为{ return _phoneNumber ?? string.Empty; }。此外,如果指定的值过长,应该抛出异常,而不是替换为任意值。 - Chris
@Chris,如果PhoneNumber不能为null(混沌熊猫的答案假设了这个情况),那么它必须在setter和构造函数中被捕获(构造函数不应该使它的对象处于无效状态)。或者,如果它可能为空,则应该在没有检查的情况下返回null(并将检查更改为允许这种情况)。 - Jon Hanna
@Chris - 就我个人而言,我认为他们的验证方式相当薄弱,但这有点跑题了。我只是无法忍受代码容易受到“NullReferenceException”攻击时不去解决它。@Jon 的评估是绝对正确的,尽管我可能会更进一步地使用一个不可变对象。 - ChaosPandion
如果你要确保将私有变量初始化为非空值,并且绝对不能在私有变量中存储空值,那么可以在setter中防止它。但是,我的一般经验是,我们所讨论的是确保引用该属性的任何外部类都不会出现空引用异常,在这种情况下,在getter中处理更加清晰简洁。 - Chris

1

1

是的,你需要。当你使用“隐式”语法快捷方式时,它会为你秘密创建一个名为_phoneNumber的后备字段。当你显式定义属性时,你需要自己创建后备字段。在你的属性定义上方放置:

private string _phoneNumber;

然后在你的属性中使用:

get
{
    return _phoneNumber;
}

我不认为它被称为那样,但它有一个又长又丑的名字。 - CodesInChaos

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