我了解Java并且通常会在代码中添加getter/setter方法。我现在想用下面这段C#代码实现同样的功能,但是它会抛出StackOverflow异常。我做错了什么?
调用代码:
c.firstName = "a";
属性代码
public String firstName;
{
get
{
return firstName;
}
set
{
firstName = value;
}
}
我了解Java并且通常会在代码中添加getter/setter方法。我现在想用下面这段C#代码实现同样的功能,但是它会抛出StackOverflow异常。我做错了什么?
调用代码:
c.firstName = "a";
属性代码
public String firstName;
{
get
{
return firstName;
}
set
{
firstName = value;
}
}
由于你在递归调用属性,因此在 set
中你再次设置属性,导致无限循环,直到堆栈溢出。
你需要一个私有的后备字段来保存值,例如:
private string firstName;
public string FirstName
{
get
{
return this.firstName;
}
set
{
this.firstName = value;
}
}
或者,如果您正在使用C# 3.0,您可以使用自动属性,它会为您创建一个隐藏的后备字段,例如:
public string FirstName { get; set; }
你正在设置属性的名称,而不是字段的名称。以下方法会更好:
private string m_firstName;
public String firstName;
{
get
{
return m_firstName;
}
set
{
m_firstName = value;
}
}
你的代码片段中无法避免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;
}
}
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("Rick Astley") => GetFirstName() => GetFirstName() => ...
FirstName
,成员变量名为firstName
。为了减少这种混淆,许多开发人员更喜欢在成员前加上_
或m_
前缀,以使其更加明显。 - PMF