.NET框架设计—常被忽视的C#设计技巧

阅读目录:html

  • 1.开篇介绍编程

  • 2.尽可能使用Lambda匿名函数调用代替反射调用(走进声明式设计)c#

  • 3.被忽视的特性(Attribute)设计方式缓存

  • 4.扩展方法让你的对象如虎添翼(要学会使用扩展方法的设计思想)安全

  • 5.别怕Static属性(不少人都怕Static在Service模式下的设计,其实要学会使用线程本地存储(ThreadStatic))性能优化

  • 6.泛型的协变与逆变(设计架构接口(Interface)时要时刻注意对象的协变、逆变)服务器

  • 7.使用泛型的类型推断(还在为参数类型烦恼吗) 数据结构

  • 8.链式编程(设计符合大脑思惟习惯的处理流程)多线程

    • 8.1.链式编程(多条件(方法碎片化)调用架构


  • 9.部分类、部分方法的使用(扩大设计范围)

1.】开篇介绍

本文中的内容都是我无心中发现以为有必要分享一下的设计经验,没有什么高深的技术,只是平时咱们可能会忽视的一些设计技巧;为何有这种想法是由于以前跟一些同事交流技术的时候会发现不少设计思惟被固化了,好比以前我在作客户端框架开发的时候会去设计一些关于Validator、DTO Transfer等经常使用的Common function,可是发如今讨论某一些技术实现的时候会被弄的云里雾里的,会自我郁闷半天,不会及时的明白对方在说的问题;

后来发现他们一是没有把概念分清楚,好比.NETFrameworkC#VisualStudio,这三者之间的关系;二是没有理解.NET中各个对象的本质含义,好比这里的特性(Attribute),大部分人都认为它是被用来做为代码说明、标识使用的,而没有突破这个思惟限制,因此在设计一些东西的时候会绕不少弯路;还有一点是不少人对C#中的语法特性分不清版本,固然咱们要大概的了解一下哪些特性或者语法是C#2的哪些是C#3的,这样在咱们设计东西的时候不会因为项目的版本问题而致使你没法使用设计技巧,好比扩展方法就没法使用在低于.NET3.0版本中,LINQ也没法在低于.NET3.O的版本中使用;

.NETFramework的版本不断的在升级,目前差很少5.0都快面世了;.NETFramework的升级跟C#的升级没有必然的关系,这个要搞清楚;C#是为了更好的与.NET平台交互,它提供给咱们的都是语法糖,最后都是.NETCTS中的类型;就好比你们都在写着LINQ,其实到最后LINQ也就被自动解析成对方法的直接调用;

2.】尽可能使用委托调用代替反射调用

委托相信你们都玩的很熟,委托的发展到目前为止是至关不错的,从本来很繁琐的每次使用委托的时候都须要定义一个相应的方法用来实例化委托,这点在后来的C#2中获得了改进,支持匿名委托delegate{…}的方式使用,再到如今的C#3那就更方便了,直接使用面向函数式的Lambda表达式;那么这样还须要反射调用对象的方法吗?(固然特殊场合咱们这里不考虑,只考虑经常使用的场景;)固然反射不是很差,只是反射须要考虑不少性能优化方面的东西,增长了代码的复杂性,也让框架变的很重(如今都是在追求轻量级,只有在DomainModel中须要将平面化的数据抽象;),因此何不使用简单方便的委托调用呢;

注:若是你是初学者,这里的委托能够理解成是咱们平时经常使用的Lambda表达式,也能够将它与Expression<T>结合起来使用,Expression<T>是委托在运行时的数据结构,而非代码执行路径;(兴趣的朋友能够查看本人的:LINQ系列文章

下面咱们来看一下演示代码:

/*==============================================================================
 * Author:深度训练
 * Create time: 2013-07-28
 * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 * Author Description:特定领域软件工程实践;
 *==============================================================================*/
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Linq;
using Infrastructure.Common.Cache;
using Infrastructure.Common.Validator;
namespace ConsoleApplication1.DomainModel
{
    /// <summary>
    /// Order.
    /// </summary>
    [EntityCache(10, true)]
    [EntityValidator(ValidatorOperationType.All)]
    public class Order
    {
        /// <summary>
        /// Order code.
        /// </summary>
        public string OrderCode { get; set; }
        /// <summary>
        /// Items filed.
        /// </summary>
        private List<Item> items = new List<Item>();
        /// <summary>
        /// Gets items .
        /// </summary>
        public IEnumerable<Item> Items { get { return items; } }
        /// <summary>
        /// Submit order date.
        /// </summary>
        public DateTime SubmitDate { get; set; }
        /// <summary>
        /// Mark <see cref="DomainModel.Order"/> Instance.
        /// </summary>
        /// <param name="orderCode">Order code. </param>
        public Order(string orderCode)
        {
            this.OrderCode = orderCode;
        }
        /// <summary>
        /// Sum items prices.
        /// </summary>
        /// <param name="itemUsingType">item type.</param>
        /// <returns>prices .</returns>
        public double SumPrices(int itemUsingType)
        {
            double resultPrices = 0.00;
            var currentItems = items.GroupBy(item => item.ItemUsingType).Single(group => group.Key == itemUsingType);
            if (currentItems.Count() > 0)
            {
                foreach (var item in currentItems)
                {
                    resultPrices += item.Price;
                }
            }
            return resultPrices;
        }
        /// <summary>
        /// Add item to order.
        /// </summary>
        /// <param name="item">Item.</param>
        /// <returns>bool.</returns>
        public bool AddItem(Item item)
        {
            if (!item.ItemCode.Equals(string.Empty))
            {
                this.items.Add(item);
                return true;
            }
            return false;
        }
    }
}


这是一个订单领域实体,它里面引用了一个Item的商品类型;

/*==============================================================================
 * Author:深度训练
 * Create time: 2013-07-28
 * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 * Author Description:特定领域软件工程实践;
 *==============================================================================*/
using System;
namespace ConsoleApplication1.DomainModel
{
    /// <summary>
    /// Order item.
    /// </summary>
    public class Item
    {
        /// <summary>
        /// Item code.
        /// </summary>
        public Guid ItemCode { get; set; }
        /// <summary>
        /// Item price.
        /// </summary>
        public float Price { get; set; }
        /// <summary>
        /// Item using type.
        /// </summary>
        public int ItemUsingType { get; set; }
    }
}

上面代码应该没有问题,基本的订单领域模型你们都太熟了;为了保证上面的代码是绝对的正确,以避免程序错误形成阅读者的不爽,因此都会有100%的单元测试覆盖率;这里咱们主要使用的是Order类中的SumPrices方法,因此它的UnitTest是100%覆盖;

图1:

Order中的SumPrices方法的UnitTest代码:

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NSubstitute;
namespace ConsoleApplication.UnitTest
{
    using ConsoleApplication1.DomainModel;
    /// <summary>
    /// Order unit test.
    /// </summary>
    [TestClass]
    public class DomainModelOrderUnitTest
    {
        /// <summary>
        /// Order sumprices using type 1 test.
        /// </summary>
        [TestMethod]
        public void DomainModelOrderUnitTest_SumPrices_ItemUsingTypeIs1_UnitTest()
        {
            Order testOrder = new Order(Guid.NewGuid().ToString());
            testOrder.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 10.0F });
            testOrder.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 15.0F });
            testOrder.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 2, Price = 20.0F });
            testOrder.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 2, Price = 30.0F });
            double result = testOrder.SumPrices(1);
            Assert.AreEqual(result, 25.0F);
        }
        /// <summary>
        /// Order sumprices using type is 2 test.
        /// </summary>
        [TestMethod]
        public void DomainModelOrderUnitTest_SumPrices_ItemUsingTypeIs2_UnitTest()
        {
            Order testOrder = new Order(Guid.NewGuid().ToString());
            testOrder.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 10.0F });
            testOrder.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 15.0F });
            testOrder.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 2, Price = 20.0F });
            testOrder.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 2, Price = 30.0F });
            double result = testOrder.SumPrices(2);
            Assert.AreEqual(result, 50.0F);
        }
    }
}


在以往我基本上不写单元测试的,可是最近工做上基本上都须要写每一个方法的单元测试,并且要求是100%覆盖,只有这样才能保证代码的正确性;也建议你们之后多写单元测试,确实颇有好处,咱们应该把单元测试作起来;下面咱们言归正传;

因为咱们的Order是在DomainModel Layer中,如今有一个需求就是在Infrastructure Layer 加入一个动态计算Order中指定Item.ItemUsingType的全部Prices的功能,其实也就是说须要将咱们的一些关键数据经过这个功能发送给远程的Service之类的;这个功能是属于Infrastructure中的Common部分也就是说它是彻底独立与项目的,在任何地方均可以经过它将DomainModel中的某些领域数据发送出去,那么这样的需求也算是合情合理,这里我是为了演示因此只在Order中加了一个SumPrices的方法,可能还会存在其余一些DomainModel对象,而后这些对象都有一些关键的业务数据须要在经过Infrastructure的时候将它们发送出去,好比发送给配送部门的Service Interface;

那么常规设计可能须要将扩展点配置出来放在指定的配置文件里面,而后当对象通过Infrastructure Layer中的指定Component时触发事件路由,而后从缓存中读取出配置的信息执行,那么配置文件可能大概是这样的一个结构:DomainEntity名称、触发动做、方法名称、参数,DomainEntity名称是肯定聚合根,触发动做是对应Infrastructure中的组件,固然你也能够放在DomainModel中;这里只关心方法名称、参数;

固然这里只演示跟方法调用相关的代码,其余的不在代码中考虑;咱们来看一下相关代码:

using System;
namespace Infrastructure.Common
{
    public interface IBusinessService
    {
        void SendBusinessData(object callAuthor, string methodName, object parameterInstance);
        void SendBusinessData<P>(Func<P, object> callFun, P parameter);
    }
}

这是业务调用接口;

using System;
using System.Reflection;
using System.Linq;
using System.Linq.Expressions;
namespace Infrastructure.Common
{
    /// <summary>
    /// Business service .
    /// </summary>
    public class BusinessService : IBusinessService
    {
        /// <summary>
        /// Send service data interface .
        /// </summary>
        private ISendServiceData sendService;
        /// <summary>
        /// Mark <see cref="Infrastructure.Common.ISendServiceData"/> instance.
        /// </summary>
        /// <param name="sendService"></param>
        public BusinessService(ISendServiceData sendService)
        {
            this.sendService = sendService;
        }
        /// <summary>
        /// Send business data to service interface.
        /// </summary>
        /// <param name="callAuthor">Object author.</param>
        /// <param name="methodName">Method name.</param>
        /// <param name="parameterInstance">Method call parameter.</param>
        public void SendBusinessData(object callAuthor, string methodName, object parameterInstance)
        {
            object result =
                callAuthor.GetType().GetMethod(methodName).Invoke(callAuthor, new object[] { parameterInstance });
            if (result != null)
            {
                sendService.Send(result);
            }
        }
        /// <summary>
        /// Send business data to service interface.
        /// </summary>
        /// <typeparam name="P"></typeparam>
        /// <param name="callFun"></param>
        /// <param name="parameter"></param>
        public void SendBusinessData<P>(Func<P, object> callFun, P parameter)
        {
            object result = callFun(parameter);
            if (result != null)
            {
                sendService.Send(result);
            }
        }
    }
}


这里简单实现IBusinessService接口,其实代码很简单,第一个方法使用反射的方式调用代码,而第二个方法则使用委托调用;在实现类里面还包含了一个简单的接口;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Infrastructure.Common
{
    public interface ISendServiceData
    {
        void Send(object sendObject);
    }
}

目的是为了方便单元测试,咱们来看一下单元测试代码;

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Infrastructure.Common;
using ConsoleApplication1.DomainModel;
using NSubstitute;
namespace Infrastructure.Common.UnitTest
{
    [TestClass]
    public class InfrastructureCommonBusinsessServiceUnitTest
    {
        [TestMethod]
        public void InfrastructureCommonBusinsessServiceUnitTest_BusinessService_SendBusinessData()
        {
            Order order = new Order(Guid.NewGuid().ToString());
            order.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 10.0F });
            order.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 15.0F });
            order.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 2, Price = 20.0F });
            order.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 2, Price = 30.0F });
            ISendServiceData mockISendServiceData = Substitute.For<ISendServiceData>();
            object sendresult = null;
            mockISendServiceData.When(isend => isend.Send(Arg.Any<object>())).Do(callinfo => sendresult = callinfo.Arg<object>());
            BusinessService testService = new BusinessService(mockISendServiceData);
            testService.SendBusinessData(order, "SumPrices", 1);
            Assert.AreEqual((double)sendresult, 25);
        }
        [TestMethod]
        public void InfrastructureCommonBusinsessServiceUnitTest_BusinessService_SendBusinessDataGen()
        {
            Order order = new Order(Guid.NewGuid().ToString());
            order.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 10.0F });
            order.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 15.0F });
            order.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 2, Price = 20.0F });
            order.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 2, Price = 30.0F });
            ISendServiceData mockISendServiceData = Substitute.For<ISendServiceData>();
            object sendresult = null;
            mockISendServiceData.When(isend => isend.Send(Arg.Any<object>())).Do(callinfo => sendresult = callinfo.Arg<object>());
            BusinessService testService = new BusinessService(mockISendServiceData);
            testService.SendBusinessData<Order>(ord => { return ord.SumPrices(1); }, order);
            Assert.AreEqual((double)sendresult, 25);
        }
    }
}

在第二个单元测试方法里面咱们将使用Lambda方式将逻辑直接注入进BusinessService中,好就好这里;能够将Lambda封进Expression<T>而后直接存储在Cache中或者配置中间,完全告别反射调用吧,就比如委托同样没有人会在使用委托在定义个没用的方法;(因此函数式编程愈来愈讨人喜欢了,能够关注一下F#;)总之使用泛型解决类型不肯定问题,使用Lambda解决代码逻辑注入;大胆的尝试吧,将声明与实现完全分离;

(对.NET单元测试有兴趣的朋友后面一篇文章会详细的讲解一下如何作单元测试,包括Mock框架的使用;)


3】被忽视的特性(Attribute)设计方式

大部分人对特性的定义是代码的“数据注释”,就是能够在运行时读取这个特性用来作类型的附加属性用的;一般在一些框架中对DomainModel中的Entity进行逻辑上的关联用的,好比咱们比较熟悉的ORM,都会在Entity的上面加上一个相似 [Table(TableName=”Order”)] 这样的特性声明,而后再在本身的框架中经过反射的方式去在运行时差找元数据找到这个特性,而后就能够对附加了这个特性的类型进行相关的处理;

这其实没有问题,很正常的设计思路,也是比较通用的设计方法;可是咱们的思惟被前人固化了,难道特性就只能做为代码的声明吗?问过本身这个问题吗?

咱们继续使用上面2】小结中的代码做为本节演示代码,如今咱们假设须要在DomainModel中的Entity上面加上两个特性第一个用来判定它是否须要作Cache,第二个用来肯定关于Entity操做验证的特性;

看代码:

/// <summary>
    /// Order.
    /// </summary>
    [EntityCache(10, true)]
    [EntityValidator(ValidatorOperationType.All)]
    public class Order
    {}

代码应该很明了,第一EntityCache用来设计实体的缓存,参数是缓存的过时时间;第二个特性EntityValidator用来设置当实体进行相关处理的时候须要的验证类型,这里选择是全部操做;

如今的问题是关于特性的优先级,对于Order类的处理究竟是先Cache而后验证,仍是先验证而后Cache或者说内部没有进行任何的逻辑处理;若是咱们将特性的视为代码的标识而不是真正的逻辑,那么对于优先级的处理会比较棘手,你须要设计如何将不一样的特性处理逻辑关联起来;比较合理的设计方法是特性的处理链表;本人以前设计过AOP的简单框架,就遇到过对于特性的优先级的处理经验,也是用的链表的方式将全部的特性按照顺序串联起来而后将对象穿过特性内部逻辑,这也符合DDD的中心思想;

下面咱们来看代码:

Codeusing System;
namespace Infrastructure.Common
{
    [AttributeUsage(AttributeTargets.Class)]
    public abstract class EntityOperation : Attribute
    {
        protected EntityOperation NextOperation { get; set; }
    }
}


咱们抽象出全部的处理,而后在内部包含下一个处理逻辑的特性实例;而后让各自的Attribute继承自它;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Infrastructure.Common.Cache
{
    [AttributeUsage(AttributeTargets.Class)]
    public class EntityCache : EntityOperation
    {
        public EntityCache(int cacheTime, bool IsEnable)
        {
            this.ExpireTime = cacheTime;
        }
        public int ExpireTime { get; set; }
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Infrastructure.Common.Validator
{
    public enum ValidatorOperationType
    {
        Insert,
        Delete,
        Update,
        Select,
        All
    }
    [AttributeUsage(AttributeTargets.Class)]
    public class EntityValidator : EntityOperation
    {
        public EntityValidator(ValidatorOperationType validatorType)
        {
        }
    }
}


根据特性在类的前后顺序就能够控制他们的优先级;

图2:

2

上图很直观的表现了链表设计思想,再经过仔细的加工应该会很不错的;

4】扩展方法让你的对象如虎添翼(要学会使用扩展方法的设计思想)

扩展方法咱们用的应该不算少的了,在一些新的框架中处处都能看见扩展方法的优点,好比:ASP.NETMVC、EntityFramework等等特别是开源的框架用的不少;

那么咱们是否是还停留在原始社会,应该尝试接受新的设计思想,尽管一开始可能不太适应,可是当你适应了以后会让你的设计思想提高一个境界;

下面咱们仍是使用上面的演示代码来进行本节的代码演示,如今假若有一个这样的需求,为了保证DomainModel的彻底干净,咱们在应用层须要对领域模型加入一些非业务性的行为,这些行为跟DomainModel自己没有直接关系,换句话说咱们这里的Order聚合实体可能须要一个获取Order在Cache中存活了多长时间的方法;那么在以往咱们可能提供一个方法而后把Order实例做为参数这样来使用,可是这里咱们的需求是该方法是Order对象的方法而不是其余地方的方法;

因此这里使用扩展方法就能够在不改变对象自己业务逻辑的状况下扩展对象行为;最关键的是扩展方法为后面的链式编程提供了基石;从长远来看DomainModel将会被独立到ThreadProcess总,当系统初始化时部分的DomainModel将直接主流在内存中,而后经过系统本地化将扩展方法加入,这样就能够在不改变对象的状况下添加行为,这也为行为驱动设计提供了好的技术实现;

用纯技术性的假设没有说服力,上面说给领域自己加上获取Cache的方法,确定会有朋友说这彻底没有必要,提供一个简单的方法就OK了,恩 我以为也有道理,那么下面的需求你将不得不说妙;

【需求简述】:对象自己的行为不是固定不变的,尤为咱们如今设计对象的时候会将对象在全局状况下的全部行为都定义在对象内部,好比咱们正常人,在不一样的角色中才具备不一样的行为,咱们只有在公司才具备“打开服务器”的行为,只有在家里才能够“亲吻”本身的老婆的行为;难道咱们在设计User类的时候都将这些定义在对象内部吗?显然不符合逻辑,更不符合面向对象设计思想,固然咱们目前基本上都是这么作的;

(有兴趣的朋友能够参考:BDD(行为驱动设计)、DCI(数据、上下文、交互)设计思想;)

如今咱们来为Order添加一组行为,可是这组 行为只有在某些场景下才能使用,这里只是为了演示而用,真要在项目中设计还须要考虑不少其余因素;

namespace DomainModelBehavior.Order.ShippingBehavior
{
    using ConsoleApplication1.DomainModel;
    public static class OrderBehavior
    {
        public static Order TaxRate(this Order order)
        {
            return order;
        }
    }
}
namespace DomainModelBehavior.Order.ShoppingCart
{
    using ConsoleApplication1.DomainModel;
    public static class OrderBehavior
    {
        public static Order Inventory(this Order order)
        {
            return order;
        }
    }
}


这里有两个位于不一样namespace中的行为,他们对应不一样的场景;第一个TaxRate用来计算税率的行为,只有在Order对象已经处于提交状态时用的;那么第二个行为Inventory用来计算库存的,用户在Shoppingcart的时候用来肯定是否有足够的库存;固然这里我只是假设;

而后咱们就能够在不一样的场景下进行命名空间的引用,好比咱们如今Shoppingcart阶段将不会使用到TaxRate行为;

using System;
namespace ConsoleApplication1
{
    using DomainModelBehavior.Order.ShoppingCart;
    class Program
    {
        static void Main(string[] args)
        {
            DomainModel.Order order = new DomainModel.Order(Guid.NewGuid().ToString());
            order.Inventory();
        }
    }
}

例子虽然有点简单,可是应该能说明扩展方法的基本使用方式,对于DCI架构的实现会复杂不少,须要好好设计才行;

5】别怕Static属性(不少人都怕Static在Service模式下的设计,其实要学会使用线程本地存储(ThreadStatic))

不少时候咱们在设计对象的时候,尤为是面向Context类型的,很但愿能经过某个静态属性直接能拿到Context,因此会定义一个静态属性用来保存对象的某个实例;可是会有不少人都会排斥静态属性,动不动就说性能问题,动不动就收多线程不安全等等借口,难道静态属性就没有存在必要了嘛;

不用静态属性你哪来的ASP.NET中的CurrentContext直接,若是怕由于多线程问题致使数据不完整,建议使用线程本地存储;没有什么好怕的,多用就熟悉了;用也很简单,直接在静态属性上面加上这个特性就OK了,前提是你已经考虑了这个属性是线程内部共享的不是应用程序级别的共享;


/// <summary>
/// 数据源的操做
/// </summary>
[ThreadStatic]
private static IDataSourceOperation datasourceoperation = IDataSourceOperationFactory.Create();

6】泛型的协变与逆变(设计架构接口(Interface)时要注意对象的协变、逆变)

愈来愈多的人喜欢本身捣鼓点东西出来用用,这很不错,时间长了设计能力天然会获得提高的;可是最近发现咱们不少泛型在设计上缺少转换的控制,也就是这里的协变和逆变;咱们有一个Item类型,如今咱们须要对它进行更加具体化,咱们派生出一个Apple类型的Item;

List<Apple> apples = new List<Apple>();
List<Item> items = apples;

这段代码是编译不经过的,由于List<T> 在定义的时候就不支持逆变、可是若是换成下面这样的代码是彻底能够的;

List<Apple> apples = new List<Apple>();
IEnumerable<Item> items = apples;

很容易的就能够获得集合的转换,虽然很简单的功能可是在设计上若是运用好的话能大大改变接口的灵活性;你可能会有一个疑问,为何具体实现List<T>不支持协变而IEnumerable<out T>反而支持协变;这就是面向对象设计的思想,接口本质是抽象的,抽象的不会有具体的实现因此它做为协变不会存在问题,可是逆变就会有问题;


7】使用泛型的类型推断(还在为参数类型烦恼吗)

在设计泛型方法的时候要学会使用类型推断技巧,这样会很方便的在调用的时候减小你显示调用<>的代码,也会显得很优美;你们应该都比较熟悉Func泛型委托,它是C#3中的主角,也是函数式编程的基础,咱们在设计某个方法的时候会将逻辑暴露在外部,而后经过Lambda的方式注入进来;如今的LINQ都是这么实现的,比较熟悉的Where方法、Select方法,都须要咱们提供一个做为它内部逻辑的函数段;

List<Item> items = new List<Item>();
            items.Add(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 20 });
            items.Add(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 20 });
            items.Add(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 20 });
            items.Add(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 20 });
            items.Where<Item>(item => item.ItemUsingType == 1);
            items.Where(item=>item.ItemUsingType==1);

这里有两种调用Where的代码,哪种看上去舒服一点有没一点,不用我说了;那咱们看一下它的定义:

public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);

咱们看到TSource类型占位符,很容易理解,这是一个扩展IEnumerable<TSource>类型的方法,系统会自动的匹配TSource;咱们在设计的时候也要借鉴这种好的设计思想;

(有兴趣的朋友能够参见本人的:.NET深刻解析LINQ框架(一:LINQ优雅的前奏)

8】链式编程(设计符合大脑思惟习惯的处理流程)

其实那么多的C#新特性都是为了能让咱们编写代码能更方便,总之一句话是为了更符合大脑思惟习惯的编程模式;

C#从纯面向对象渐渐的加入了函数式模式,从静态类型逐渐加人动态类型特性;C#如今变成多范式编程语言,其实已经很大程度知足咱们的平常需求;以往咱们都会为了动态行为编写复杂的Emit代码,用不少CodeDom的技术;如今可使用Dymanic解决了;

这节咱们来看一下关于如何设计线性的链式方法,这不是技术问题,这是对需求的理解能力;能够将链式思想用在不少地方,只要有逻辑有流程的地方均可以进行相关设计,首先你要保证你是一个正常思考问题的人,别设计出来的方法是反的,那么用的人会很不爽的;这里我举一个我最近遇到的问题;

8.1】链式编程(多条件(方法碎片化)调用

咱们都熟悉DTO对象,它是从UI传过来的数据集合,简单的业务逻辑Application Layer将它转换成DomainModel中的Entity,若是复杂的业务逻辑是不能直接将DTO进行转换的;可是在转换过程当中咱们老是少不了对它的属性判断,若是UserName不为空而且Password不为空我才能去验证它的合法性,等等;相似这样的判断;这里咱们将运行扩展方法将这些逻辑判断链起来,而且最后输出一个完整的Entity对象;

using System.Collections.Generic;
namespace ConsoleApplication1
{
    public static class DtoValidator
    {
        public static IEnumerable<TSource> IsNull<TSource>(this IEnumerable<TSource> tList)
        {
            return tList;
        }
        public static IEnumerable<TSource> IsLength<TSource>(this IEnumerable<TSource> tList, int max)
        {
            return tList;
        }
        public static IEnumerable<TResult> End<TResult>(this IEnumerable<object> tList, IEnumerable<TResult> result)
        {
            result = new List<TResult>();
            return result;
        }
    }
}

有一组扩展方法,用来作验证用的;

List<Order> orderList = null;
List<Dto.OrderInfoDto> dtoList = new List<Dto.OrderInfoDto>();
dtoList.IsNull().IsLength(3).End(orderList);

因为时间关系我这里只是演示一下,彻底能够作的很好的,在判断的最后拿到返回的列表引用最后把数据送出来;

(有一个开源验证框架应该还不错,目前工做中在用:FluentValidator)

9】部分类、部分方法的使用(扩大设计范围)

部分类不是新的特性,而部分方法是新特性;咱们经过灵活运用部分类能够将发挥很大做用,好比咱们彻底能够将类的部分实现彻底隔离在外部,起到低耦合的做用,甚至能够将声明式设计元编程运用在C#中,比较经典就是ASP.NET后台代码和前台的模板代码,在运行时而后再经过动态编译合起来,咱们不要忘记可使用部分类、部分方法来达到在运行时连接编译时代码和运行时代码,相似动态调用的效果;

因为这部份内容比较简单,是设计思想的东西,因此没有什么要演示的,只是一个总结;


总结:内容虽然简单,可是要想运用的好不简单,这里我只是总结一下,但愿对你们有用,谢谢;


示例DEMO地址:http://files.cnblogs.com/wangiqngpei557/ConsoleApplication1.zip


做者:王清培

出处:http://wangqingpei557.blog.51cto.com/

本文版权归做者和51CTO共有,欢迎转载,但未经做者赞成必须保留此段声明,且在文章页面明显位置给出原文链接,不然保留追究法律责任的权利。

相关文章
相关标签/搜索