有趣的属性行为

5

有人知道为什么这个不起作用吗(无论是C#、VB.NET还是其他.NET语言都一样)。这是我问题的一个非常简化的例子(对于VB.NET我很抱歉):

    Private itsCustomTextFormatter As String
    Public Property CustomTextFormatter As String
        Get
            If itsCustomTextFormatter Is Nothing Then CustomTextFormatter = Nothing  'thinking this should go into the setter - strangely it does not'
            Return itsCustomTextFormatter
        End Get
        Set(ByVal value As String)
            If value Is Nothing Then
                value = "Something"
            End If
            itsCustomTextFormatter = value
        End Set
    End Property

如果您这样做:

Dim myObj as new MyClass
Console.WriteLine(myObj.CustomTextFormatter)

您将会对结果感到惊讶。它将打印“Nothing”。有人知道为什么它没有打印“Something”吗?

以下是一个单元测试,根据建议进行:

Imports NUnit.Framework

<TestFixture()> _
Public Class Test
   Private itsCustomTextFormatter As String
    Public Property CustomTextFormatter As String
        Get
            If itsCustomTextFormatter Is Nothing Then CustomTextFormatter = Nothing 'thinking this should go into the setter - strangely it does not' 
            Return itsCustomTextFormatter
        End Get
        Set(ByVal value As String)
            If value Is Nothing Then
                value = "Something"
            End If
            itsCustomTextFormatter = value
        End Set
    End Property

    <Test()>
    Public Sub Test2()
        Assert.AreEqual("Something", CustomTextFormatter)
    End Sub
End Class

这将返回:
Test2 : Failed  
  Expected: "Something"
  But was:  null

at NUnit.Framework.Assert.That(Object actual, IResolveConstraint expression, String message, Object[] args)
at NUnit.Framework.Assert.AreEqual(Object expected, Object actual)

1
在C#中,您可以在setter中执行value = null; - Andras Zoltan
这段代码是一个完美的例子,展示了如何永远不要去做。在 Return itsCustomTextFormatter 处设置断点,并告诉我们完整的故事 :) - Daniel Mošmondor
@AndrasZoltan 哇,我真的很惊讶。我不知怎么就以为它是常数。 - CodesInChaos
3
value 是指代 变量 的关键字。当然,你可以重新分配变量的值。 - Eric Lippert
@CodeInChaos:确实,这让很多人感到惊讶。关于值类型需要认识的一点是,在值类型S中,void M() { this.x = 123; }只是写作优雅的方式,实际上等同于 static void M(ref S _this) { _this.x = 123; }this必须是调用接收器相同的变量,否则可变值类型就不可能存在。 - Eric Lippert
显示剩余7条评论
3个回答

13

你的评论:

'thinking this should go into the setter - strangely it does not'    

调用会告知您的错误是什么。在Visual Basic中,有两种方法可以从函数中返回值:

Function GetSomeValue() As String
    Return "Hello"
End Function
或者
Function GetSomeValue() As String
    GetSomeValue = "Hello"
End Function
混合这两种样式是完全合法的,但容易混淆,不好的做法。
Function GetSomeValue() As String
    GetSomeValue = "Hello" ' I should return Hello... '
    Return "GoodBye"       ' ...or perhaps not. '
End Function

正如您所看到的,您在getter中混合了这两种风格。 设置该变量不会调用setter; 它告诉运行时,当getter返回时,应该返回的是这个值。 然后您使用Return语句覆盖了该值。

如果这样做让您感到疼痛,则不要这样做


1
@Denis 是的,有的。设置 [函数/属性名称] = [某个值] 实际上是分配了一个隐式返回值,如果在方法结束之前没有其他 Return 命令存在,则在方法末尾返回该值。 - Mike Guthrie
1
@Denis 设置 CustomTextFormatter = Nothing 是告诉 CustomTextFormatter 如果没有其他返回语句,则默认返回 Nothing。但是,你还没有到达那一步,因为你实际上到达了 Return itsCustomTextFormatter,其中 itsCustomTextFormatter 的值仍然是 nothing,因为尚未调用 Set。 - Mike Guthrie
1
@Denis 从getter中调用setter是设计不良的。话虽如此,你可以通过明确声明 Me.CustomerTextFormatter = Nothing 来实现这一点。 - Mike Guthrie
1
@GuthMD:你说得太对了!该死的VB.NET - 你又抓住了我!!!就是这样 - 这将成为某个可怜家伙的面试问题! - Denis
1
@CodeInChaos:从getter中调用setter确实是一种奇怪的做法,但有时确实会发生。特别是当涉及到惰性计算的值时,getter可能会检查该值是否已经计算过,如果是,则返回它。如果没有,则计算它,调用setter缓存该值以备下次使用,并返回它。 - Eric Lippert
显示剩余4条评论

4

在我的C#单元测试中,这个代码可以正常工作。

(经过一些尝试之后)

啊,我知道了!原因是你调用了CustomTextFormatter = Nothing,然而在Getter的范围内,实际上只是设置了封装访问器方法的返回值,它并没有实际触发Setter(如果你在Setter中放置一个断点并调试它,你会看到它直接跳过了Setter)。

基本上,你真的不应该使用这种模式来返回默认属性值。这样做更好(或者使用与C# ??运算符等效的内容):

Public Property CustomTextFormatter As String
    Get
        If itsCustomTextFormatter Is Nothing Then
            Return "Something"
        End If
        Return itsCustomTextFormatter

    End Get
    Set(ByVal value As String)
        itsCustomTextFormatter = value
    End Set
End Property

原始的C#测试

    private string _foo;
    private string foo
    {
        get
        {
            if (_foo == null)
                foo = null;
            return _foo;
        }
        set
        {
            if (value == null)
                value = "Something";
            _foo = value;
        }
    }

    [TestMethod]
    public void Test()
    {
        Assert.AreEqual("Something", foo);
    }

@Denis - 好的,首先我们注意到的是它不是 .Net - 它要么是特定于 VB,要么(更可能)是特定于 你的 VB 代码。我们只需要找出原因。 - Andras Zoltan
我看到了。这是一个语法问题。该死的VB.NET又抓住了我!!!@Eric是对的:"foo = Nothing"在VB.NET中用于“getter”或函数时相当于“return Nothing”。在C#中,“foo = Nothing”表示调用“setter”。 - Denis
1
@Denis,C#与VB不同,因为它们是两种不同的编程语言。如果你想知道为什么VB有Return<functionname> = <value>技术:这是为了向后兼容旧版本的VB,因为旧版本的VB没有Return语句来返回函数值。在新代码中,你应该优先选择使用Return语句。 - MarkJ
1
@Denis 是的,Me.CustomTextFormatter 可以工作,因为它明确地引用了 class 范围内的属性。而 CustomTextFormatter 单独使用时则使用 getter 的隐式范围,在这个范围内,名称具有特定的含义,即函数本身的返回值。虽然有点复杂,但它是语言的一部分,所以嘿-嗨 :D - Andras Zoltan
1
@Denis,如果你了解VB 6及更早版本,这就有意义了。这些语言没有Return关键字。相反,函数有一个隐含的变量使用函数名来保存返回值。换句话说,要从Function SomeFunction() As Integer返回7,您必须有一个类似SomeFunction = 7的语句。同样令人困惑的是,属性getter也是如此,因此为了避免这种情况,您必须使用Me.PropertyName = valueWithWhichToCallSetter。VB.NET保留了语法,同时添加了Return关键字。 - phoog
显示剩余7条评论

-3

因为您从未对变量进行初始化。您必须将属性"Set"为"nothing"才能真正获得"Something"。为了修复它,您应该在声明内部变量时设置默认值。

Private itsCustomTextFormatter As String = "Something"

我不理解你的推理。 - CodesInChaos
不确定这与什么相关 - Denis

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