重构代码时,我会遇到如下这样的实例
private string _property = string.Empty;
public string Property
{
set { _property = value ?? string.Empty); }
}
稍后在一个方法中,我看到以下内容:
if (_property != null)
{
//...
}
假设_property
只由Property
的设置器设置,那么这个代码是多余的吗?
有没有办法,通过反射魔法或其他方法,_property
可以为空?
假设_property仅由property的setter设置,这是代码冗余?
没错,这是多余的。这就是Properties的实际用途。我们不应该直接访问类的字段。我们应该使用Property访问它们。因此,在相应的setter中,我们可以嵌入任何逻辑,并且我们可以放心,每次我们尝试设置值时,该逻辑都会被再次验证。这个参数甚至适用于类的方法。在一个方法中,我们必须使用属性,而不是实际的字段。此外,当我们想要读取字段的值时,我们应该使用相应的getter。
一般来说,属性增强了封装的概念,封装是面向对象编程OOP的支柱之一。
很多时候,当我们想要设置一个值时,没有任何逻辑应该应用。举个例子:
public class Customer
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
我们已经声明了一个用于表示客户的类。Customer对象应该有三个属性:Id
、FirstName
和LastName
。
当有人读到这个类时,一个直接的问题是,为什么有人要使用这里的属性?
答案也是一样的,它们提供了一种封装机制。但让我们考虑一下,从长远来看,这对我们有什么帮助。假设有一天,有人决定客户的名字应该是一个长度小于20的字符串。如果上述类别已声明如下:
public class Customer
{
public int Id;
public string FirstName;
public string LastName;
}
那么我们应该在我们创建的每个实例中检查FirstName
的长度!否则,如果我们选择了带有属性的声明,我们就可以很容易地使用数据注释
public class Customer
{
public int Id { get; set; }
[StringLength(20)]
public string FirstName { get; set; }
public string LastName { get; set; }
}
就是这样。另一种方法可能是:
public class Customer
{
public int Id { get; set; }
private string firstName;
public string FirstName
{
get { return firstName }
set
{
if(value!=null && value.length<20)
{
firstName = value;
}
else
{
throw new ArgumentException("The first name must have at maxium 20 characters", "value");
}
}
}
public string LastName { get; set; }
}
考虑以上两种方法,必须重新访问所有的代码库并进行检查。很明显,房地产是赢家。
是的,通过反射是可能的。尽管如此,我并不担心反思——我不担心有人用反思来破坏你课的设计。
然而,我确实担心的是:"假设_property只由property的setter设置"这句话是关键。您正在阻止类的用户将属性设置为null。
但是,您不能阻止您自己或类的其他维护者忘记只使用类内部的属性。事实上,您的示例中有人从类内部检查字段,而不是属性本身。。。。这意味着,在类中,访问来自字段和属性。
在大多数情况下(问题只能来自类内部),我会使用断言并断言字段不是null。
如果我真的,真的,我真的想确保它不是空的(除非反思或人们一心想破坏东西),你可以试试这样的东西:
internal class Program
{
static void Main()
{
string example = "Spencer the Cat";
UsesNeverNull neverNullUser = new UsesNeverNull(example);
Console.WriteLine(neverNullUser.TheString);
neverNullUser.TheString = null;
Debug.Assert(neverNullUser.TheString != null);
Console.WriteLine(neverNullUser.TheString);
neverNullUser.TheString = "Maximus the Bird";
Console.WriteLine(neverNullUser.TheString);
}
}
public class UsesNeverNull
{
public string TheString
{
get { return _stringValue.Value; }
set { _stringValue.Value = value; }
}
public UsesNeverNull(string s)
{
TheString = s;
}
private readonly NeverNull<string> _stringValue = new NeverNull<string>(string.Empty, str => str ?? string.Empty);
}
public class NeverNull<T> where T : class
{
public NeverNull(T initialValue, Func<T, T> nullProtector)
{
if (nullProtector == null)
{
var ex = new ArgumentNullException(nameof(nullProtector));
throw ex;
}
_value = nullProtector(initialValue);
_nullProtector = nullProtector;
}
public T Value
{
get { return _nullProtector(_value); }
set { _value = _nullProtector(value); }
}
private T _value;
private readonly Func<T, T> _nullProtector;
}
它基本上是多余的。然而,如果它是关键任务,或者由于某种原因造成了可怕的副作用,它可能会继续存在。这很难说,但你的部分问题是"反射能把这个值改为null吗",答案是肯定的,可以在这个linqpad演示中看到
void Main()
{
var test = new Test();
test.Property = "5";
Console.WriteLine(test.Property);//5
FieldInfo fieldInfo = test.GetType().GetField("_property",BindingFlags.NonPublic | BindingFlags.Instance);
fieldInfo.SetValue(test, null);
Console.WriteLine(test.Property);//null
}
public class Test
{
private string _property = string.Empty;
public string Property
{
get { return _property; }
set { _property = value ?? string.Empty; }
}
}
我知道这个问题很老了,但我需要我的一个字符串属性永远不会出现null。
所以我做了这个,它对我起到了作用
public string Operation { get; set; } = string.Empty;
通过这种方式,默认值是一个空字符串,但从不为null。