now*_*ed. 180 c# null .net-4.0 .net-4.5
假设,我有这个界面,
interface IContact
{
IAddress address { get; set; }
}
interface IAddress
{
string city { get; set; }
}
class Person : IPerson
{
public IContact contact { get; set; }
}
class test
{
private test()
{
var person = new Person();
if (person.contact.address.city != null)
{
//this will never work if contact is itself null?
}
}
}
Run Code Online (Sandbox Code Playgroud)
Person.Contact.Address.City != null
(这可以检查City是否为空.)
但是,如果Address或Contact或Person本身为null,则此检查将失败.
目前,我能想到的一个解决方案是:
if (Person != null && Person.Contact!=null && Person.Contact.Address!= null && Person.Contact.Address.City != null)
{
// Do some stuff here..
}
Run Code Online (Sandbox Code Playgroud)
这样做有更干净的方法吗?
我真的不喜欢这样做的null
检查(something == null)
.相反,还有另一种很好的方法来做这样的something.IsNull()
方法吗?
Tot*_*oto 236
以通用方式,您可以使用表达式树并使用扩展方法进行检查:
if (!person.IsNull(p => p.contact.address.city))
{
//Nothing is null
}
Run Code Online (Sandbox Code Playgroud)
完整代码:
public class IsNullVisitor : ExpressionVisitor
{
public bool IsNull { get; private set; }
public object CurrentObject { get; set; }
protected override Expression VisitMember(MemberExpression node)
{
base.VisitMember(node);
if (CheckNull())
{
return node;
}
var member = (PropertyInfo)node.Member;
CurrentObject = member.GetValue(CurrentObject,null);
CheckNull();
return node;
}
private bool CheckNull()
{
if (CurrentObject == null)
{
IsNull = true;
}
return IsNull;
}
}
public static class Helper
{
public static bool IsNull<T>(this T root,Expression<Func<T, object>> getter)
{
var visitor = new IsNullVisitor();
visitor.CurrentObject = root;
visitor.Visit(getter);
return visitor.IsNull;
}
}
class Program
{
static void Main(string[] args)
{
Person nullPerson = null;
var isNull_0 = nullPerson.IsNull(p => p.contact.address.city);
var isNull_1 = new Person().IsNull(p => p.contact.address.city);
var isNull_2 = new Person { contact = new Contact() }.IsNull(p => p.contact.address.city);
var isNull_3 = new Person { contact = new Contact { address = new Address() } }.IsNull(p => p.contact.address.city);
var notnull = new Person { contact = new Contact { address = new Address { city = "LONDON" } } }.IsNull(p => p.contact.address.city);
}
}
Run Code Online (Sandbox Code Playgroud)
Kev*_*ica 62
您的代码可能比需要检查空引用有更大的问题.就目前而言,你可能违反了得墨忒耳法则.
Demeter法是其中一种启发式方法,比如"不要重复自己",它可以帮助您编写易于维护的代码.它告诉程序员不要访问距离直接范围太远的任何东西.例如,假设我有这样的代码:
public interface BusinessData {
public decimal Money { get; set; }
}
public class BusinessCalculator : ICalculator {
public BusinessData CalculateMoney() {
// snip
}
}
public BusinessController : IController {
public void DoAnAction() {
var businessDA = new BusinessCalculator().CalculateMoney();
Console.WriteLine(businessDA.Money * 100d);
}
}
Run Code Online (Sandbox Code Playgroud)
该DoAnAction
方法违反了得墨忒耳定律.在一个函数中,它访问a BusinessCalcualtor
,a BusinessData
和a decimal
.这意味着如果进行以下任何更改,则必须重构该行:
BusinessCalculator.CalculateMoney()
更改.BusinessData.Money
变化的类型考虑到现状,这些变化很可能发生.如果在整个代码库中编写这样的代码,那么进行这些更改可能会变得非常昂贵.此外,它意味着你的BusinessController
连接到两个BusinessCalculator
及BusinessData
类型.
避免这种情况的一种方法是重写代码,如下所示:
public class BusinessCalculator : ICalculator {
private BusinessData CalculateMoney() {
// snip
}
public decimal CalculateCents() {
return CalculateMoney().Money * 100d;
}
}
public BusinessController : IController {
public void DoAnAction() {
Console.WriteLine(new BusinessCalculator().CalculateCents());
}
}
Run Code Online (Sandbox Code Playgroud)
现在,如果您进行上述任一更改,则只需重构一段代码即BusinessCalculator.CalculateCents()
方法.你也消除了BusinessController
对它的依赖BusinessData
.
您的代码遇到类似的问题:
interface IContact
{
IAddress address { get; set; }
}
interface IAddress
{
string city { get; set; }
}
class Person : IPerson
{
public IContact contact { get; set; }
}
class Test {
public void Main() {
var contact = new Person().contact;
var address = contact.address;
var city = address.city;
Console.WriteLine(city);
}
}
Run Code Online (Sandbox Code Playgroud)
如果进行了以下任何更改,您将需要重构我编写的main方法或您编写的null检查:
IPerson.contact
变化的类型IContact.address
变化的类型IAddress.city
变化的类型我认为你应该考虑对代码进行更深入的重构,而不仅仅是重写null检查.
也就是说,我认为有时候遵循得墨忒耳法是不合适的.(毕竟,它是一种启发式的,而不是一种强硬的规则,即使它被称为"法律".)
特别是,我认为如果:
在专门处理这些类时,忽略了Demeter法则是可以接受的.这是因为它们代表了应用程序使用的数据,因此从一个数据对象到另一个数据对象是一种探索程序中信息的方法.在我上面的例子中,违反Demeter法则引起的耦合要严重得多:我从堆栈顶部附近的控制器一直到达堆栈中间的业务逻辑计算器到可能的数据类在持久层中.
我把这种潜在的例外迪米特法则,因为有相似的名字Person
,Contact
和Address
,你的类看起来像他们可能是数据层波苏斯.如果是这种情况,并且您非常有信心将来永远不需要重构它们,那么您可以在特定情况下忽略Demeter法则.
Kor*_*ryu 48
在您的情况下,您可以为人创建一个属性
public bool HasCity
{
get
{
return (this.Contact!=null && this.Contact.Address!= null && this.Contact.Address.City != null);
}
}
Run Code Online (Sandbox Code Playgroud)
但你还是要检查一下这个人是否为空
if (person != null && person.HasCity)
{
}
Run Code Online (Sandbox Code Playgroud)
对于你的其他问题,对于字符串,你也可以检查是否为null或以这种方式清空:
string s = string.Empty;
if (!string.IsNullOrEmpty(s))
{
// string is not null and not empty
}
if (!string.IsNullOrWhiteSpace(s))
{
// string is not null, not empty and not contains only white spaces
}
Run Code Online (Sandbox Code Playgroud)
big*_*gge 37
一个完全不同的选项(我认为未充分利用)是空对象模式.在你的特定情况下很难判断它是否有意义,但它可能值得一试.简而言之,您将拥有一个NullContact
实现,一个NullAddress
实现等等,而不是使用null
.这样,您可以摆脱大多数空检查,当然牺牲一些人认为您必须将这些实现的设计付诸实践.
正如亚当在评论中指出的那样,这可以让你写作
if (person.Contact.Address.City is NullCity)
Run Code Online (Sandbox Code Playgroud)
在真的有必要的情况下.当然,只有城市真的是一个非平凡的对象,这才有意义......
或者,null对象可以实现为单例(例如,在这里查看有关空对象模式的使用的一些实用指令,这里有关于C#中单例的指令),它允许您使用经典比较.
if (person.Contact.Address.City == NullCity.Instance)
Run Code Online (Sandbox Code Playgroud)
就个人而言,我更喜欢这种方法,因为我觉得对不熟悉模式的人来说更容易阅读.
Ada*_*rth 26
更新28/04/2014: 计划为C#vNext进行空传播
传播空检查存在更大的问题.目标是可以被另一个开发人员理解的可读代码,虽然它很冗长 - 你的例子很好.
如果是经常进行的检查,请考虑将其Person
作为属性或方法调用封装在类中.
那说,无偿Func
和泛型!
我永远不会这样做,但这是另一种选择:
class NullHelper
{
public static bool ChainNotNull<TFirst, TSecond, TThird, TFourth>(TFirst item1, Func<TFirst, TSecond> getItem2, Func<TSecond, TThird> getItem3, Func<TThird, TFourth> getItem4)
{
if (item1 == null)
return false;
var item2 = getItem2(item1);
if (item2 == null)
return false;
var item3 = getItem3(item2);
if (item3 == null)
return false;
var item4 = getItem4(item3);
if (item4 == null)
return false;
return true;
}
}
Run Code Online (Sandbox Code Playgroud)
所谓的:
static void Main(string[] args)
{
Person person = new Person { Address = new Address { PostCode = new Postcode { Value = "" } } };
if (NullHelper.ChainNotNull(person, p => p.Address, a => a.PostCode, p => p.Value))
{
Console.WriteLine("Not null");
}
else
{
Console.WriteLine("null");
}
Console.ReadLine();
}
Run Code Online (Sandbox Code Playgroud)
Mar*_*zek 14
第二个问题,
我真的不喜欢执行null检查(某些事情== null).相反,有没有另一种很好的方法来做一些像something.IsNull()方法?
可以使用扩展方法解决:
public static class Extensions
{
public static bool IsNull<T>(this T source) where T : class
{
return source == null;
}
}
Run Code Online (Sandbox Code Playgroud)
San*_*zen 10
如果出于某种原因你不介意选择其中一个"超过顶级"的解决方案,你可能想看看我博客文章中描述的解决方案.在评估表达式之前,它使用表达式树来确定值是否为null.但为了保持性能可接受,它会创建并缓存IL代码.
该解决方案允许您写这个:
string city = person.NullSafeGet(n => n.Contact.Address.City);
Run Code Online (Sandbox Code Playgroud)
小智 7
你可以写:
public static class Extensions
{
public static bool IsNull(this object obj)
{
return obj == null;
}
}
Run Code Online (Sandbox Code Playgroud)
然后:
string s = null;
if(s.IsNull())
{
}
Run Code Online (Sandbox Code Playgroud)
有时这是有道理的.但我个人会避免这样的事情...因为这不清楚为什么你可以调用实际为null的对象的方法.
在另外一个单独的做method
:
private test()
{
var person = new Person();
if (!IsNull(person))
{
// Proceed
........
Run Code Online (Sandbox Code Playgroud)
你的IsNull
method
位置
public bool IsNull(Person person)
{
if(Person != null &&
Person.Contact != null &&
Person.Contact.Address != null &&
Person.Contact.Address.City != null)
return false;
return true;
}
Run Code Online (Sandbox Code Playgroud)