为什么属性设置会引发StackOverflow异常?

68

我了解Java并且通常会在代码中添加getter/setter方法。我现在想用下面这段C#代码实现同样的功能,但是它会抛出StackOverflow异常。我做错了什么?

调用代码:

c.firstName = "a";

属性代码

public String firstName;
{
    get
    {
        return firstName;
    }
    set
    {
        firstName = value;
    }
}
3个回答

131

由于你在递归调用属性,因此在 set 中你再次设置属性,导致无限循环,直到堆栈溢出。

你需要一个私有的后备字段来保存值,例如:

private string firstName;

public string FirstName
{
    get
    {
        return this.firstName;
    }
    set
    {
        this.firstName = value;
    }
}

或者,如果您正在使用C# 3.0,您可以使用自动属性,它会为您创建一个隐藏的后备字段,例如:

public string FirstName { get; set; }

1
不知道如何调用“隐藏的后备字段”。感谢自动属性。 - broadband
我简直不敢相信这是一个解决方案……我对C#的信心几乎已经消失殆尽了。 - Daniel
1
哎呀,如果你认为那很糟糕,我就不想看到你写Java。 - Lucas Leblanc
请问一下,它是如何导致堆栈溢出的呢?不是在 setter 中相同的值漫游吗? - Arli Chokoev
@ArliChokoev 不是的,请注意大小写:属性名为FirstName,成员变量名为firstName。为了减少这种混淆,许多开发人员更喜欢在成员前加上_m_前缀,以使其更加明显。 - PMF
@ArliChokoev 我发布了一个新答案,涉及为什么堆栈会溢出的原因 ;) - Dorin Baba

15

你正在设置属性的名称,而不是字段的名称。以下方法会更好:

private string m_firstName;

public String firstName;
{
    get
    {
        return m_firstName;
    }
    set
    {
        m_firstName = value;
    }
}

0

你的代码片段中无法避免StackOverflowExeption异常,这就是原因。

为了理解这一点,我们必须知道属性是什么,以及为什么需要使用它们。基本上,属性是一组方法,而不是字段。通过“属性”,我们指的是设置方法(设置某些内容的值)和获取方法(返回某些内容的值)的集合。

属性为我们提供了一定的灵活性,当涉及到使用上述方法来给私有字段分配值时,可以在此基础上添加附加处理。

你的代码无法正常工作,因为你不断地调用get方法,导致栈溢出,这就像一个没有退出条件的递归函数(当我们尝试获取FirstName的值时,属性调用其get方法,该方法返回自身,然后再次调用get方法,重复这个过程,直到你的堆栈填满这些东西)。

如果在类中有一个私有字段,你可以使用该字段周围的属性将其公开,并且我们可以为get和set方法添加附加处理。以下是一个示例:假设我们想要获取数字(0到9)的ASCII码,我们可以使用属性来实现(这不是最佳示例,只是为了说明属性的一些用法):

// this is the private field that we do not want to expose (aka to make
// it accessible for everyone
private int digit;

// But we want somehow to allow users of our class to interact 
// with the digit field, so we create a property.
// Note: fields' name start with lowercase, properties' name with Uppercase
public int Digit
{
   get 
   {
       // this is the method that gets calls whenever the user calls Digit,
       // Example: Console.WriteLine(object.Digit);

       // let's add here the logic of getting digit's ascii code
       return Char.Parse(digit.ToString());
   }

   set
   {
       // this method gets called when someone assigns a value for Digit
       // Example: Digit = 3;
     
       // here we can add the validation logic ( 0 <= value <=9)
       if(value < 0 || value > 9)
          throw new Exception("Please provide a number between 0 and 9");
    
       digit = value;
    }
}  

我们使用Digit属性将数字在被访问时转换为ASCII,并验证试图分配给我们的数字字段的值。
在您的情况下,您可以这样编写FirstName属性:
public string FirstName{  get; set; }

在幕后,这行代码将被视为与以下代码相同:

private string firstName;
public string FirstName
{
   get { return firstName; }
   set { firstName= value }
}

更详细地说明为什么堆栈溢出。

许多语言不像C#一样具有属性,所以人们只能添加单独的get和set方法。有了这个模型,更容易发现溢出发生的位置。假设C#中没有属性,我们创建一个私有字段和两个方法——一个用于设置,另一个用于获取私有字段的值。

private string firstName;

public string GetFirstName()
{
    return firstName;
}

public void SetFirstName(string value)
{
   firstName = value;
}

现在他有一个setter和getter,它们没有包装在属性中。为了看到问题示例中发生溢出的位置,让我们将其适应到我们没有属性的新环境中。

public string GetFirstName()
{
   return GetFirstName();
}

public void SetFirstName(string value)
{
   GetFirstName() = value; 
}

这就是你的代码所做的事情。没有私有字段可以玩耍,属性自己玩耍^^

当我们写下这段代码时:

object.SetFirstName("Rick Astley");

我们进入SetFirstName方法,该方法调用GetFirstName(),然后出现死循环。将'=>'视为“调用”:

SetFirstName("Rick Astley") => GetFirstName() => GetFirstName() => ...


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