代码改变世界

【More Effective C#】Partial Class是怎样炼成的?

2010-10-28 08:14  空逸云  阅读(2804)  评论(13编辑  收藏

什么是部分类(Partial Class)?

C#中.我们可以利用部分类,将一个类分散到多个类文件中,这样我们就可以多个开发者同时开发某个类库,或者是扩展其他开发者发布的类库.甚至是代码生成器生成的代码,例如LINQ2SQL,ADO.NET EF等,以获取更高效的开发.

Re:Class和Class File的区别.这里的类是我们平时所说的普通类-Class,如抽象类,基类,子类等等.而类文件-Class File则是我们平时编写类时所用到的文件,如C#的.cs,VB的.vb.

虽然部分类很好的解决了多个开发者同时开发而互不影响的问题,但其约束十分严格.开发者之间不能/很难彼此沟通,并且无法/很难修改对方的代码.这意味着需要为对方提供很多挂钩.这些挂钩应该以部分方法的方式实现.而另一方的开发者可以选择实现或不实现.

扩展现有类

实体框架的出现,大大的减轻了我们的工作量,使我们把更多的重心放在逻辑实现上,而减少对于数据访问的关注.例如我们可以使用LINQ2SQL,LINQ2SQL是一个轻型的实体框架.它生成了我们对数据层的访问.但.有些时候,我们需要扩展些自己的内容,如方法/属性等等.除了直接在生成的类文件中添加修改意外,我们还可以利用部分类扩展我们所需的功能,前者当每次我们跟新实体时.你所扩展的内容都会消失.而部分类依然能很好的为你服务.

提供部分方法

为部分类提供最有效的钩子就是部分方法.它和接口的方法声明很相似.

关键字partial+void+方法名+方法参数.

partial void ReportValueChanging(RequestChange args);

由于部分方法可能成为类的一部分(实现),也可能不成为类的一部分(不实现),所以C#本身对于部分方法给出了一些限制.

1.返回类型必须为void.

2.部分方法不能为抽象/虚方法.

3.部分方法不能用来实现接口.

4.参数列表不能包含任何out参数,因为编译器无法初始化这些out参数.

我们利用部分方法来让用户件事或修改类的行为:修改方法,事件处理程序及构造函数.

修改方法

修改方法是指那些将要修改类中对外可见的状态的方法.我们可以将其理解为任何状态的变化,由于另一个类文件的部分类实现依然是类的一部分.所以我们可以完全控制到该类的所有内部状态.

一般而言,修改方法应该为类提供两个部分方法.第一个在修改前调用,用来让另一方(另一开发者)能够进行合法性验证等等.第二个方法应该在修改状态后调用,用来让另一方相应这个状态的变化.

public partial class GeneratedStuff
    {
        private struct ReportChange
        {
            public readonly int OldValue;
            public readonly int NewValue;

            public ReportChange(int oldValue, int newValue)
            {
                this.OldValue = oldValue;
                this.NewValue = newValue;
            }
        }

        private class RequestChange
        {
            public ReportChange Values
            {
                get;
                set;
            }
            public bool Cancel
            {
                get;
                set;
            }
        }

        partial void ReportValueChanging(RequestChange args);
        partial void ReportValueChanged(ReportChange values);

        private int storage = 0;

        public void UpdateValue(int newValue)
        {
            RequestChange updateArgs = new RequestChange
            {
                Values = new ReportChange(storage, newValue)
            };
            ReportValueChanging(updateArgs);
            if (!updateArgs.Cancel)
            {
                storage = newValue;
                ReportValueChanged(new ReportChange(storage, newValue));
            }
        }
    }

如果另一个部分类没有提供两个部分方法的实现,那么C#编译器将移除该调用.生成方法如下

public void UpdateValue(int newValue)
        {
            RequestChange updateArgs = new RequestChange 
            {
                Values = new ReportChange(this.storage, newValue)
            };
            if (!updateArgs.Cancel)
            {
                this.storage = newValue;
            }
        }

并且,两个部分方法也一并移除了.

我们可以实现挂钩如下

public partial class GeneratedStuff
        {
            partial void ReportValueChanging(GeneratedStuff.RequestChange args)
            {
                if (args.Values.NewValue < 0)
                {
                    //dosomething...
                }
                else
                {
                    //dosomething...
                }
            }

            partial void ReportValueChanged(GeneratedStuff.ReportChange values)
            {
                //dosomething...
            }
        }

这里,我们通过一个取消标记来让开发者可以取消某个修改动作,你也可以通过抛出异常得到同样的效果.但.推荐使用布尔型的取消标记,因为这样更加轻量一些

事件处理程序

实际上,事件的处理程序和上面的方法修改并无多大的区别.你所需要做的.仅仅只是添加一个委托事件而已.实现了类似控件的事件机制.

构造函数扩展

无论是生成的代码,还是我们手工编写的代码.我们都无法预计调用类时外部将会调用哪个构造函数.所以.我们只能在内部做一些手脚.例如

public partial class GeneratedStuff
        {
            partial void Initialize();
            public GeneratedStuff()
                : this(0)
            { }

            public GeneratedStuff(int someValue)
            {
                this.storage = someValue;
                Initialize();
            }
        }

注意!Initialize在最后调用,这样就有了一次检查当前对象状态的机会,可以进行必要的修改,或是对于不符合要求时抛出异常等等.实际上.这种构造函数重载也有效的提升你的代码质量(对于不同的构造函数,结果IL都会生成不同的函数块.而这种重构法最后其实真正生成的函数块只有一个)

尾声

经过以上的阐述,相信我们已经更了解部分类是怎样炼成的了.只有更好的了解其背后的一系列"小动作",才有助于我们更好的提升,写出更有质量的代码