处理不应为null的C#属性

时间:2009-03-06 16:27:51

标签: c# properties

为我正在处理的项目设置一些引用类型属性时,我遇到了一些需要正确初始化才能使用的属性,并且永远不应为null。我已经看到了一些方法来处理这个问题,并且无法确定我看到处理这个问题的主要方法是否存在任何重大缺陷。我想得到社群对处理此问题的最佳方法的看法,以及每种方法可能存在的缺点。

给定一个简单的类,我已经看到了几种方法来处理确保属性永远不会在属性中具有此类的null版本

public class MyClass
{
  //Some collection of code
}

选项1 - 初始化后备存储

public class OtherClass1
    {
        private MyClass _mC = new MyClass();
        public MyClass MC
        {
            get { return _mC; }
            set { _mC = value; }
        }
    }

选项2 - 在构造函数中初始化属性

public class OtherClass2
    {
        public MyClass MC { get; set; }     

        public OtherClass2()
        {
            MC = new MyClass(); 
        }
    }

选项3 - 根据需要处理Getter中的初始化

public class OtherClass3
    {
        private MyClass _mC;
        public MyClass MC
        {
            get
            {
                if (_mC == null)
                    _mC = new MyClass();
                return _mC; 
            }
            set { _mC = value; }
        }
    }

我确信还有其他方法,但这些是我想到的那些想到的。我主要是试图确定是否有一个完善的最佳实践,或者是否存在与上述任何一个特定关注的问题。

干杯,

史蒂夫

8 个答案:

答案 0 :(得分:6)

最好的选择,除非你真的可以自己创建一个新实例:只提供获取所有必需值的构造函数,并在那时验证它们。

答案 1 :(得分:2)

据我所知,是一种既定的最佳实践,原因很简单:每个选项都有不同的性能/内存占用情况。第一个选项适用于对您必须在您确定将使用的类中实例化的对象的引用。但老实说,我从不采用这种方法,因为我认为#2更合适;只是感觉这是构造函数 for

确定是否使用选项时,最后一个选项是合适的。它允许您仅在需要时占用资源。

顺便说一下,这个问题正好与其他一些问题“隔壁”,例如适当使用Singleton模式,使用抽象类或接口作为延迟对象等等,这些问题可能对您有用。探索以获得更大的洞察力。

更新:我觉得至少有一种情况是在类定义中初始化一个实例是合适的(你的选项#1)。如果实例是静态的,那么这是 初始化它的唯一合适位置:

private static readonly DateTime firstClassDate = DateTime.Parse("1/1/2009 09:00:00 AM");

在我今天写的一些单元测试中创建上面的代码行时,我想到了这一点( readonly 是我的观点可选的,但在我的情况下是合适的。)

答案 2 :(得分:1)

解释我几天前发布的一个问题,但我认为强制执行代码规则并确保在您不需要的地方不使用Nulls可能会有所帮助:

Microsoft刚刚发布了Code Contracts,这是一个与Visual Studio集成的工具,允许您为.Net代码定义合同并获取运行时编译时检查。

观看显示其使用方式的video on Channel 9

目前它是一个附加组件,但它将成为.Net 4.0中基类库的一部分

答案 3 :(得分:0)

假设关于何时实例化_mC没有副作用(即,其他所有相等),我更喜欢选项3,因为在getter的情况下,它可以节省堆上MyClass的其他实例的开销。 MC从未被召唤过。

答案 4 :(得分:0)

选项(3)的好处是在需要之前不分配对象,它可以很容易地适应作为延迟加载的对象(所以当从数据库加载对象时 - 保持一个外键来加载需要时提供完整的子对象)

答案 5 :(得分:0)

选项1& 2在语法上是不同的但基本上是等同的。 选项3是一种懒惰的初始化方法,我非常一致地使用它 我认为它们都有它们的用途,这取决于你需要什么。

答案 6 :(得分:0)

首先应该在初始化时属性永远不为null或永远不为null?我怀疑你的意思是前者,在这种情况下你的setter代码需要阻止设置空值。

这里的基本模式是,如果内部类字段没有有效的赋值,则外部类状态无效。在这种情况下,不仅应该让setter保护字段为null,而且构造函数应该确保将其初始化为正确的值。

您的代码暗示外部类可以在没有消费代码的进一步输入的情况下实例化内部类。在现实世界中,外部类需要来自外部的进一步信息,以便接收内部类的现有实例或足够的信息来检索内部类。

答案 7 :(得分:0)

选项1是香草的方式。从回到过去,我们没有自动实现的属性(使用{get; set;} sintax),因此他们可以指定默认行为。

当引入自动实现时,由于您没有直接使用存储默认值(_mC)的字段,因此会出现一个相当不错的问题:“我在哪里初始化它?”

  • 选项2被称为急切加载:创建课程后立即开始。
  • 选项3被称为延迟加载:仅在需要时立即执行。

我已经看到普遍接受的方式是选项2:急切加载但我相信这只是为了最小化代码,这是完全可以接受的。当你开始有多个具有多个签名的构造函数并且你最终将它们全部指向某种 void Initialize()方法时会出现问题。

我特别喜欢选项3 ,因为它更符合每个字段/属性的声明,并且是内存优化的。

看看具有不同风格的EntityFramework:Code-First或Database-first,您会注意到内置属性如何使用急切的加载器,而对于导航属性,它有利于(默认情况下,可以自定义)惰性加载器。

此外,请考虑此装载程序中发生的情况。在实体框架中,它意味着每次初始化它都会访问数据库并查询其中的一部分。您可能会因为DBA多次同时进行会话并且可能希望将某些事务集中在单个有效负载中,因此您可能会很快就会出现问题,因此有些重新布线可能会发挥作用......尽管您最终会查询大量的数据并减慢您的用户体验。

代码优先中,您可以看到以下示例:

public class Blog
{ 
    public int BlogId { get; set; } 
    public string Name { get; set; } 
    public string Url { get; set; } 
    public string Tags { get; set; } 

    public virtual ICollection<Post> Posts { get; set; } 
}

在上面,Posts集合被声明为虚拟,因为它将在运行时根据您的设置进行修改。如果您将其设置为预先加载,则会将其设置为选项2 ,而如果将其设置为延迟,则会将其修改为类似于选项3

希望这很有用