C#基础:接口(三)

    本文将简要介绍接口的显式实现。

    先看下面的代码:

interface IInterfaceA
{
    int GetValue(int x);
}

interface IInterfaceB
{
    int GetValue(int x);
}

class Concrete : IInterfaceA, IInterfaceB
{
    
}

    在上面的代码中,Concrete类同时实现了IInterfaceA和IInterfaceB。由于IInterfaceA和IInterfaceB有着同样的函数签名,此时如果Concrete类以实例方法来实现GetValue函数,势必会导致无论是用IInterfaceA的实例还是IInterfaceB的实例来调用GetValue函数,都会产生同样的结果。比如下面的代码会输出两个20:

interface IInterfaceA
{
    int GetValue(int x);
}

interface IInterfaceB
{
    int GetValue(int x);
}

class Concrete : IInterfaceA, IInterfaceB
{
    public int GetValue(int x)
    {
        return x + 10;
    }
}

class Program
{
    static void Main(string[] args)
    {
        IInterfaceA ia = new Concrete();
        Console.WriteLine(ia.GetValue(10));

        IInterfaceB ib = new Concrete();
        Console.WriteLine(ib.GetValue(10));
    }
}

    此时,两个接口公用了同一个函数实现。然而在更多的情况下,虽然两个接口有着相同签名的函数,但我们仍希望针对不同接口有着各自不同的函数实现。接口的显式实现就是解决这样的问题。请看下面的代码:

interface IInterfaceA
{
    int GetValue(int x);
}

interface IInterfaceB
{
    int GetValue(int x);
}

class Concrete : IInterfaceA, IInterfaceB
{
    int IInterfaceA.GetValue(int x)
    {
        return x + 10;
    }

    int IInterfaceB.GetValue(int x)
    {
        return x + 20;
    }
}

class Program
{
    static void Main(string[] args)
    {
        IInterfaceA ia = new Concrete();
        Console.WriteLine(ia.GetValue(10));

        IInterfaceB ib = new Concrete();
        Console.WriteLine(ib.GetValue(10));
    }
}

    上面的代码输出了20和30,达到了我们的要求。需要注意的是,如果直接使用Concrete类的实例,是无法调用到GetValue函数的,因为类里面没有名称为GetValue的实例函数(Instance Method)。

    从实现上看,接口的显式实现具有下面的格式:

  • 函数以<接口名>.<函数在接口中的名称>命名,例如上面的IInterfaceA.GetValue和IInterfaceB.GetValue
  • 接口的显式实现函数不能带访问修饰符(比如public,protected等)

    显式接口还有一些妙用,比如,针对未提供泛型版本的接口,为其提供类型安全机制,并能有效避免繁杂的装箱、拆箱操作。有关这方面的详细描述读者可以参考《CLR via C#》一书或其它文献资源。时间关系,我就不在这详述了。

转载至:http://www.cnblogs.com/daxnet/archive/2009/05/12/1686979.html

发表在 C# | 留下评论

【WCF】更改DataContractSerializer的输出格式

    WCF所支持的序列化格式分为Xml和DataContract两种(以下简称Xml格式和DataContract格式)。个人认为,Xml序列化格式是为了达到协议兼容而保留下来的传统格式。比如通过WCF实现基于xup协议的UI架构时,为了与xup协议保持兼容,在设置WCF属性时,建议采用Xml格式;而DataContract格式则是WCF提供的一种新的序列化格式。默认情况下,WCF采用DataContract格式。如需要在WCF中使用Xml格式,我们所要做的事情非常简单:在服务契约接口以及相应的契约方法上应用XmlSerializerFormatAttribute特性,并在使用svcutil.exe实用工具生成客户端代码时带上/ser:XmlSerializer参数。Xml格式和DataContract格式的优缺点大致如下:

  • Xml格式:序列化方式可以通过XmlRootAttribute、XmlElementAttribute以及XmlAttributeAttribute等特性进行控制,也可以使用XmlAttributeOverrides类对序列化方式进行更深层次的自定义,因此它能很方便地兼容已有的通讯协议,使得应用程序符合一定的通讯标准;其缺点就是速度不及DataContract格式,对于需要序列化的成员,必须是公有成员,而且同时实现getter和setter
  • DataContract格式:速度比Xml格式快,因为它正好缺少Xml格式的优点:无法自定义序列化方式。其优点是,相对Xml格式来讲,它支持更多类型的序列化,而且其成员的受访级别可以是受保护的,甚至是私有的;DataContract格式没有Xml格式中类似XmlElementAttribute等控制序列化方式的特性 

    因此,当你的应用程序无需兼容标准协议,并且你不需要对传输的信息作调试时,建议采用DataContract格式。但也有时候我们既希望保证速度,又希望能够在应用程序里输出response的xml信息以便查错;此时,我们会使用DataContractSerializer类来将获得的response信息序列化成xml。当走到这一步时,你会发现,你获得的xml信息会是下面的样子:

view plaincopy to clipboardprint?
  1. <?xml version=“1.0” encoding=“utf-8”?><ApResponse xmlns:i=“http://www.w3.org/2001/XMLSchema-instance” xmlns=“http://schemas.datacontract.org/2004/07/Apworks.UI.Protocols.Response”><SessionId>55b494dc-430d-407e-9c5b-62ff9cc86804</SessionId><Type>Startup</Type><ServerInfo><Name>Apworks UI Experimental Server</Name><Version>1.0</Version></ServerInfo><StartupInfo><WindowList xmlns:d3p1=“http://schemas.microsoft.com/2003/10/Serialization/Arrays”><d3p1:string>wndHelloWorld</d3p1:string><d3p1:string>wndComplex</d3p1:string></WindowList></StartupInfo><InSessionInfo><ElementHolderXml i:nil=“true” /><EventSelectors i:nil=“true” /><UIAttrUpdates i:nil=“true” /></InSessionInfo><ShutdownInfo /><ErrorInfo><Message i:nil=“true” /></ErrorInfo></ApResponse>  

     其实,我们可以使用XmlWriter和XmlWriterSettings类来自定义xml的输出格式。通过类似下面的代码(注意粗体部分),我们获得了带有缩进效果的xml字符串,增加了xml的可读性。

view plaincopy to clipboardprint?
  1. this.clientProxy.Processed += (sender, e) =>       
  2. {       
  3.     XmlWriterSettings settings = new XmlWriterSettings { Indent = true, Encoding=Encoding.UTF8 };     
  4.   
  5.     settings.IndentChars = “\t”;       
  6.     DataContractSerializer s = new DataContractSerializer(typeof(ApResponse));       
  7.     MemoryStream ms = new MemoryStream();       
  8.     XmlWriter writer = XmlWriter.Create(ms, settings);       
  9.     s.WriteObject(writer, e.Response);       
  10.     writer.Flush();       
  11.     writer.Close();       
  12.     string xml = Encoding.ASCII.GetString(ms.ToArray());       
  13.     this.DoProcessed(sender, xml);       
  14. };     

输出效果如下:

  1. <?xml version=“1.0” encoding=“utf-8”?>      
  2. <ApResponse xmlns:i=“http://www.w3.org/2001/XMLSchema-instance” xmlns=“http://schemas.datacontract.org/2004/07/Apworks.UI.Protocols.Response”>      
  3.     <SessionId>5fab9547-06c3-4fc9-8e20-43eafa3bb74a</SessionId>      
  4.     <Type>Startup</Type>      
  5.     <ServerInfo>      
  6.         <Name>Apworks UI Experimental Server</Name>      
  7.         <Version>1.0</Version>      
  8.     </ServerInfo>      
  9.     <StartupInfo>      
  10.         <WindowList xmlns:d3p1=“http://schemas.microsoft.com/2003/10/Serialization/Arrays”>      
  11.             <d3p1:string>wndHelloWorld</d3p1:string>      
  12.             <d3p1:string>wndComplex</d3p1:string>      
  13.         </WindowList>      
  14.     </StartupInfo>      
  15.     <InSessionInfo>      
  16.         <ElementHolderXml i:nil=“true” />      
  17.         <EventSelectors i:nil=“true” />      
  18.         <UIAttrUpdates i:nil=“true” />      
  19.     </InSessionInfo>      
  20.     <ShutdownInfo />      
  21.     <ErrorInfo>      
  22.         <Message i:nil=“true” />      
  23.     </ErrorInfo>      
  24. </ApResponse>    

转载至:http://www.cnblogs.com/daxnet/archive/2009/05/22/1686977.html

发表在 C# | 留下评论

C#中事件的动态调用

    今天遇到一个问题,就是希望能够动态调用事件。传统的思路是,通过Reflection.EventInfo获得事件的信息,然后使用GetRaiseMethod方法获得事件被触发后调用的方法,再使用MethodInfo.Invoke来调用以实现事件的动态调用。

    很不幸,Reflection.EventInfo.GetRaiseMethod方法始终返回null。这是因为,C#编译器在编译并处理由event关键字定义的事件时,根本不会去产生有关RaiseMethod的元数据信息,因此GetRaiseMethod根本无法获得事件触发后的处理方法。Thottam R. Sriram 在其Using SetRaiseMethod and GetRaiseMethod and invoking the method dynamically 一文中简要介绍了这个问题,并通过Reflection.Emit相关的方法来手动生成RaiseMethod,最后使用常规的GetRaiseMethod来实现事件触发后的方法调用。这种做法比较繁杂。以下代码是一个简单的替代方案,同样可以实现事件的动态调用:

view plaincopy to clipboardprint?
  1. public event EventHandler<EventArgs> MyEventToBeFired;   
  2. public void FireEvent(Guid instanceId, string handler)       
  3. {         
  4.     // Note: this is being fired from a method with in the same class that defined the event (i.e. “this”).           
  5.     EventArgs e = new EventArgs(instanceId);   
  6.     MulticastDelegate eventDelagate = (MulticastDelegate)this  
  7.       .GetType()   
  8.       .GetField(handler, BindingFlags.Instance | BindingFlags.NonPublic)
  9.       .GetValue(this);   
  10.     Delegate[] delegates = eventDelagate.GetInvocationList();   
  11.     foreach (Delegate dlg in delegates)   
  12.     {   
  13.         dlg.Method.Invoke( dlg.Target, new object[] { this, e } );   
  14.     }   
  15. }   
  16. FireEvent(new Guid(), “MyEventToBeFired”);   

转载至:http://www.cnblogs.com/daxnet/archive/2009/05/22/1686976.html

发表在 C# | 留下评论

Predicate<T>与Func<T, bool>泛型委托

    先看下面的例子:

view plaincopy to clipboardprint?
  1. static void Main(string[] args)   
  2. {   
  3.     List<string> l = new List<string>();   
  4.     l.Add(“a”);   
  5.     l.Add(“b”);   
  6.     l.Add(“s”);   
  7.     l.Add(“t”);   
  8.   
  9.     if (l.Exists(s => s.Equals(“s”)))   
  10.     {   
  11.         string str = l.First(s => s.Equals(“s”));   
  12.         Console.WriteLine(str);   
  13.     }   
  14.     else  
  15.         Console.WriteLine(“Not found”);   
  16. }   

    非常简单,就是先判断字符串列表l中是否有s字符串,如果有,则取之并显示出来。从代码中可以看到,l.Exists方法和l.First方法所使用的参数是相同的,但事实是否真是如此?

    事实上,List<T>.Exists和List<T>.First的参数分别使用了不同的委托:Predicate<T>和Func<T, bool>。从函数的签名上看,两者没有区别,都是指代的参数类型为T,返回值为bool的函数,但毕竟两者属于不同的委托类型,因此,下面的代码显然是无法编译通过的:

view plaincopy to clipboardprint?
  1. static void Main(string[] args)   
  2. {   
  3.     List<string> l = new List<string>();   
  4.     l.Add(“a”);   
  5.     l.Add(“b”);   
  6.     l.Add(“s”);   
  7.     l.Add(“t”);   
  8.     Func<string, bool> p = s => s.Equals(“s”);   
  9.     if (l.Exists(p))   
  10.     {   
  11.         string str = l.First(p);   
  12.         Console.WriteLine(str);   
  13.     }   
  14.     else  
  15.         Console.WriteLine(“Not found”);   
  16. }   

    然而,由于Predicate<T>和Func<T, bool>的确指代的是同一类具有相同签名的函数,而我们往往又不希望将匿名方法的方法体重复地写两次以分别赋予Predicate<T>和Func<T, bool>泛型委托,因此,我们可以自己写一个扩展方法,扩展Func<T, bool>类型以使其能够很方便的转换成Predicate<T>类型:

view plaincopy to clipboardprint?
  1. public static class Extensions   
  2. {   
  3.     public static Predicate<T> ToPredicate<T> (this Func<T, bool> source)   
  4.     {   
  5.         Predicate<T> result = new Predicate<T>(source);   
  6.         return result;   
  7.     }   
  8. }   

    在引入了这个扩展方法之后,我们的代码就可以写成下面的形式:

view plaincopy to clipboardprint?
  1. static void Main(string[] args)   
  2. {   
  3.     List<string> l = new List<string>();   
  4.     l.Add(“a”);   
  5.     l.Add(“b”);   
  6.     l.Add(“s”);   
  7.     l.Add(“t”);   
  8.     Func<stringbool> p = s => s.Equals(“s”);   
  9.     if (l.Exists(p.ToPredicate()))   
  10.     {   
  11.         string str = l.First(p);   
  12.         Console.WriteLine(str);   
  13.     }   
  14.     else  
  15.         Console.WriteLine(“Not found”);   
  16. }   

     说实话不知为何MS要用这样两种完全不同的泛型委托来实现Exists和First方法,这使得某些情况下代码变得相对复杂,甚至容易出错。我想大概是为了语义清晰的缘故,Exists不过是做判断,因此需要用断言表达式,而在做First操作的时候,则更多的意义上是在迭代地调用指定的方法。

转载至:http://www.cnblogs.com/daxnet/archive/2009/05/26/1686975.html

发表在 C# | 留下评论

初探.NET 4.0中的Entity Framework

    今天无意中看到一篇文章,介绍了.NET Framework 4.0中的Entity Framework的设计与使用。文章开头部分提到了Database First和Model First两种应用程序设计方式。从.NET Framework 3.5 SP1开始,Database First的思想就开始伴随我们,它支持反向工程,可以将数据库中的表反向生成支持DLinq的模型。而从即将到来的.NET Framework 4.0开始,我们就可以以Model First的方式处理问题,即通过白手起家设计模型,而后再生成数据库结构,以便模型对象能够持久化到数据库中。

    看到这里,我备感欣慰。.NET Framework在领域驱动设计上终于迈进了一大步。在我的《【领域驱动设计】.NET实践:立足领域》一文中,我早就提到,软件设计必须立足领域,而不要去管数据库是什么结构、用的什么技术。从.NET 3.0的Database First到.NET 4.0的Model First,这就是进步,.NET从4.0开始,已经慢慢走向领域驱动设计的正路。

    Microsoft ADO.NET Team Blog的这篇文章详细介绍了如何通过模型来生成数据库结构,这让我们对于Entity Framework存在的现实意义有了个直观的认识。微软在通过模型生成数据库结构这一功能上是下了功夫的,用户可以随心所欲地定制DDL的生成过程。不过遗憾的是,从目前来看,Entity Framework没法应对模型变更的情形,例如系统用了一段时间后发现需要更改某些业务逻辑,从而不得不对模型进行修改,这样一来,目前的Entity Framework只能是通过CreateDatabase调用来重新生成数据库结构,而无法动态地对其进行增量修正。

    针对这一问题,Entity Framework开发组的Noam Ben-Ami项目经理回答如下:

1. We do not offer this capability from the designer. The intent is that existing Microsoft or third party tools be used to diff the DDL against the database.(我们不会从设计器上去提供这样的功能,这是因为,已有的Microsoft或者第三方工具可以通过比对DDL和数据库结构来解决这个问题)

2. The system is sufficiently extensible that this functionality can be added in. We may release bits that do this as a codeplex project (or some other public release mechanism.)(Entity Framework具有足够的扩展性,添加这样的功能是完全可行的,我们可能会通过codeplex或者其它的公共发布机制来发布这样的功能)

3. We have gotten sufficient feedback from many sources on this that we will investigate making this functionality a code part of the product as soon as is reasonably possible.(针对这一问题,我们已经通过各种渠道获得了足够的反馈信息,在条件成熟时,我们会对是否将这部分功能做到产品里进行调研)

    总体来讲,我很高兴Microsoft能在DDD上走出如此重要的一步。

转载至:http://www.cnblogs.com/daxnet/archive/2009/07/29/1686972.html

发表在 C# | 留下评论

【领域驱动设计】事务脚本、活动记录和领域模型

【本贴转载并翻译自 When to use domain driven development and database driven development

    出于某种原因,Martin Fowler在其PoEAA一书中介绍了三种不同的模式:事务脚本(transaction script)、活动记录(active record)以及领域模型(Domain Model)。领域驱动设计使用的是领域模型模式,并引入了大量实现这种模型的模式与实践。

    Transaction script是一种没有任何分层结构的模式,在这种模式中,数据库的访问、数据的处理以及用户界面的处理都由同一段代码完成。

    与Transaction Script相比,Active Record往前迈进了一步,它将用户界面作为单独的一层,从其它内容中剥离出来,但你的业务逻辑和数据访问仍紧密地绑定在一起,使得你不得不根据数据库来建模你的活动记录。

    Domain Model则将你的领域模型从数据访问层中解耦出来,整个领域模型对数据访问一无所知。

    OK,现在我们来对问题进行进一步分析:

    这样的分层解耦自然会带来一些额外工作量,但同时也使得应用程序更具可维护性与可扩展性。

    当你的应用很少具有,甚至没有业务逻辑的时候,你可以选用Transaction Script模式。你只需要读写数据,而无需对其进行任何校验,或者整个校验过程是运行在数据库端的。

    Active Record则带来一些灵活性,因为你可以将UI从应用程序中解耦出来,从而可以使得相同的UI使用不同的应用机制,你也可以很方便地向业务对象中添加一些业务规则和数据校验机制。但由于模型仍然与数据库紧耦合,因此数据模型的更改会使你付出很大的代价。

    当你需要将业务逻辑从数据库解耦出来时,你可以选择使用Domain Model模式。这使你能够很方便地掌控应用程序的需求变更。领域驱动设计是一种方法,它能够使你更好地将这种灵活性应用在极为复杂的应用程序解决方案上,而无需关心数据库实现的具体细节。

    现在市面上有很多工具都可以使得数据库驱动的开发过程变得更加简单。例如,微软提供了可视化的网站设计解决方案,每张页面都与一份代码关联起来,这是一种非常典型的Transaction Script应用,开发简单的应用程序变得非常方便;Ruby on Rails具有支持Active Record的工具。由于这两种模式都有工具支持,我想,这大概是很多人愿意使用数据库驱动开发的主要原因。对于那些行为比数据更重要、更需要应对需求变更的复杂系统而言,领域驱动设计就是不错的选择。

【注:Sunny Chen所译,引用请注明出处。为使文章不至于那么生硬,译文在原文的基础上做了些小的变动,但在主要观点上未作任何夸辞渲染】

转载至:http://www.cnblogs.com/daxnet/archive/2009/08/03/1686971.html

发表在 C# | 留下评论

初探.NET 4.0中的Entity Framework(二)

上个月试装了Visual Studio 2010 BETA1版本,内置.NET 4.0。Entity Framework(EF)是其中的一个非常重要的框架,在上文中我也介绍过,从整体上看,Entity Framework已经开始让.NET逐步走向了领域驱动设计时代。

事实上Visual Studio 2008 Service Pack 1中已经带了一个Entity Framework的BETA版本,但遗憾的是,这个版本的Entity Framework与领域驱动设计思想仍然相去较远,更贴近于原来的LINQ to SQL,理由是,它不支持值对象。

Visual Studio 2010 BETA1中的Entity Framework版中引入了ComplexType,这就是对值对象的很好支持。ComplexType由一个和/或多个ScalarType/ComplexType组成,在做关系型数据库映射时,会被映射到数据表的多个字段,这个特性与NHibernate中的component相似。

从目前的情形看,我仍然觉得Visual Studio 2010 BETA1中的Entity Framework有以下需要考虑或者改进的地方:

  • 对枚举类型的支持。当前的版本不支持枚举类型,其实枚举是领域模型中很重要的数据类型。比如:销售订单的状态(OrderStatus)可以有:新建、已提货、已装箱、已发货、已出发票、已退货等,那么在做建模的时候,使用包括有Created, Picked, Packed, Shipped, Invoiced, Rejected等成员的枚举,会使得模型通俗易懂;然而,目前的EF不支持枚举,设计人员不得不暂时使用int等基本数据类型来取代
  • 对“模型”概念的定义。目前的EF允许用户在Entity Data Model Designer上设计模型,并能保存成edmx文件。然而在这个设计器上所看到的“模型”跟实际的领域模型还是有差距的,比如对模型对象操作的定义,目前还是无法体现在设计器上,设计器上的模型主要还是一种对象化的数据库体现形式,也就是跟数据库关系比较密切。领域驱动设计中很重要的一条就是,属于领域对象的操作,应该尽量放在类的定义中,而不仅仅是POCO/POJO。那么现在,开发人员只能通过部分类partial class的特性来实现这样的功能。理由很简单:模型类都是由Visual Studio自动生成的
  • 仍然是上文中我的观点:模型的更改很难自动地映射到持久化机制上。详情请见这篇文章

从总体上,我对EF的感受基本上就是这些,今后,我会从领域驱动设计的角度来进一步探讨Entity Framework对DDD的支持,相信到时候会有更多的收获。

转载至:http://www.cnblogs.com/daxnet/archive/2009/08/29/1686969.html

发表在 C# | 留下评论

C#基础:多功能的接口

C#接口有三种用途:

  1. 提供方法、属性、事件的抽象。这是接口最常见的用途。在面向对象的设计中,接口是软件架构可扩展性的重要保证因素,与抽象类配合使用,使得框架结构具有“被注入”的特性,从而提高系统的扩展性
  2. 用作泛型约束。在这种情况下,接口可以仅仅是一个类型,其中可以不带任何方法、属性或事件的定义。请看:
    view plaincopy to clipboardprint?
    1. public interface IChargeable   
    2. {   
    3. }   
    4.   
    5. public class MyCharge : IChargeable { }   
    6.   
    7. public class ChargeFacility<TChargeable>    
    8.     where TChargeable : IChargeable { }  

    在这个例子中,IChargeable仅仅是为了提供一个泛型约束,确保ChargeFacility中的泛型类型是实现了IChargeable接口的(或者是IChargeable接口本身)。在这种情况下,IChargeable完全可以不需要提供任何方法、属性或事件的定义。

  3. 接口用作开发约束。由于反射的引入,在某些应用场合,程序本身很可能不是通过判断某个类是否实现了某个接口,再去调用给定的函数;程序完全可以使用反射来找到它所需要调用的函数然后使用MethodInfo.Invoke方法去调用这个函数。那么这样一来,开发人员有可能忘记在类中实现这些特定的方法,而导致程序出现问题。于是可以添加一个接口,在其中定义好必须实现的函数声明,然后通过Best Practice的方式要求开发人员在这些类上实现这个接口,那么开发人员也就不得不去填写函数实现体了。

 

转载至:http://www.cnblogs.com/daxnet/archive/2009/09/10/1686967.html

发表在 C# | 留下评论

C#基础:泛型的接口抽象

    一个比较有趣的问题:某程序在启动之前,需要对三种不同类型的数据的交叉引用(Cross Reference,xref)进行更新,现假设每种xref组件(xref component)都专门负责一种类型数据的交叉引用更新。交叉引用更新程序(XRefUpdator)在被构造的时候,会通过反射列举出当前assembly中的所有components,以便在更新的时候,逐一调用这些components的相应方法,完成对所有数据的更新。

    很明显,为了强化类型和扩展方便,我们会定义一个xref component的泛型类,泛型类型就是交叉引用数据(XRefData)。由此,出现如下定义:

public interface IXRefData          
{         
    // TODO: add definition here         
}         

public class XRefComponent<TData>         
    where TData : IXRefData         
{         
    public void Update() { }        
}         

public class XRefPickData : IXRefData { }       

public class XRefPackData : IXRefData { }

好了,现在考察XRefUpdator的定义。根据上面的设计思路,XRefUpdator需要包含一个xref component的列表,以便在需要的时候进行遍历从而逐一更新数据:

public class XRefUpdator   
{   
    private List<XRefComponent<......   
}  
 

    OK,到这里就傻眼了,上面的省略号部分不知道该怎么写,到底是XRefComponent<XRefPickData>还是XRefComponent<XRefPackData>,事实上两者都不是。由此引出了一个非常明显但又容易忽视的概念:泛型表达的是一组完全不同的类型。既然是不同的类型,我们也不可能将它们统一地装入一个列表当中。

    如何解决这样的问题?其实方法很简单,就是引入接口。废话不说了,看完下面的代码后,您就会豁然开朗:

public interface IXRefData             
{            
    // TODO: add definition here            
}    

public interface IXRefComponent   
{   
    void Update();   
}           

public class XRefComponent<TData> : IXRefComponent   
    where TData : IXRefData            
{            
    public void Update() { }            
}            

public class XRefPickData : IXRefData { }          

public class XRefPackData : IXRefData { }         

public class XRefUpdator   
{   
  private List<IXRefComponent> components = new List<IXRefComponent>();   

  public XRefUpdator   
  {   
    foreach(Type type in this.GetType().Assembly.GetExportedTypes())   
    {   
      if (typeof(IXRefComponent).IsAssignableFrom(type))   
      {   
        components.Add((IXRefComponent)Activator.CreateInstance(type));   
      }   
    }   
  }   
}  
 

    到这里,我想您应该觉得事情已经结束了。事实上并非如此。如果XRefComponent<TData>类中的Update方法需要用到TData类型的参数,或者返回值是TData类型,那么,你就无法简单地使用IXRefComponent接口去做抽象,因为你没有办法在这个接口中定义Update方法。如果你使用泛型接口,那又到了问题的起点:你遇到的是一堆完全不同的接口。在后续的文章中,我会介绍如何解决这个问题。

转载至:http://www.cnblogs.com/daxnet/archive/2009/09/14/1686966.html

发表在 C# | 留下评论

WCF服务开发中的SecurityNegotiationException异常

在WCF开发中可能会遇到这样的问题,即在连接服务器的时候,程序抛出SOAP SecurityNegotiationException的异常。内容大致如下:

我估计这个问题跟计算机处于某个域/Active Directory环境有关,当计算机连接到域时,并不会出现这样的问题,但如果计算机脱离了域,虽然能够使用域账号成功登录到计算机,但在启动WCF客户端时则会出现本文所说的异常。

开发人员可以尝试将客户端程序的配置文件中的如下行:

<userPrincipalName value="XXXX" />

改为:

<servicePrincipalName value="host/localhost" />

转载至:http://www.cnblogs.com/daxnet/archive/2009/11/13/1686965.html

发表在 C# | 留下评论

在VS.NET2008中使用并发布Crystal Reports ActiveX组件

Visual Studio 2008自带的Crystal Reports Basic版本,事实上它的ActiveX组件版本是10.5的,关键的组件有两个:craxddrt.dll和crviewer.dll。本文简要介绍如何在C#项目中使用Crystal Reports Basic的ActiveX组件,以及如何发布带有该组件的项目。

基本思路是使用craxddrt.dll中的ApplicationClass打开一个报表,然后将报表实例赋给crviewer.dll的ActiveX控件以显示报表,现假设报表文件(*.rpt)中没有任何参数设置,数据库连接使用*.rpt模板中的默认连接。要在C#中打开一个rpt报表,首先需要添加对craxddrt.dll的COM引用:

然后,打开需要添加report viewer的Windows Forms窗体,在工具栏上点右键添加如下工具:

C#代码大致如下:

using CRAXDDRT; 
ApplicationClass applicationClass = new ApplicationClass();       
Report report = applicationClass.OpenReport(@"c:\test.rpt", null);       
reportViewer1.ReportSource = report;       
reportViewer1.ViewReport();  

现在开始制作安装程序。制作安装程序的时候,注意Visual Studio 2008的Setup Project向导会自动分析你代码的关联性(Dependencies),然后会把craxddrt.dll和crviewer.dll放在“Detected Dependencies”节点下,并会将这两个文件复制到安装目录中。此时,记得在File System Editor中,分别将这两个文件的Register属性改为vsdrfCOM:

编译Setup Project后生成Setup.msi文件。

部署的时候,客户机上需要首先安装Crystal Reports的组件,安装文件可以在开发机器的%program files%\Microsoft SDKs\windows\v6.0A\Bootstrapper\Packages\CrystalReports10_5目录中找到。找到后,在客户机器上首先运行CRRedist2008_x86.msi(32位)或CRRedist2008_x64.msi(64位)安装程序,再运行刚刚编译得到的Setup.msi文件即可。

转载至:http://www.cnblogs.com/daxnet/archive/2009/11/17/1686964.html

发表在 C# | 留下评论

文件批处理系统 – GULU

这是我一年半以前用Visual Studio 2005开发的一款文件批处理系统。当时希望能够对一系列的图片文件进行批量处理,于是处于锻炼自己软件设计能力的目的,花了一个月的时间着手开发了这个软件。当然,作为单机版的文件批处理软件,在实用性方面并不具备太多亮点,但它更多的却是在软件扩展性和定制化方面的功能。仍然还是那句话,它是我“练手”的一个作品。今天在整理机器的时候发现了这个软件,我便装上了它,“重温”了它的一些功能亮点,也在此向各位有兴趣的朋友做个简单的预览和介绍。

当时在完成这个软件后,我在微软支持的开源站点CodePlex为其建立了一个项目,地址是:http://gulu.codeplex.com,上面有这个软件的源代码。需要源代码的朋友请上该网站下载。

 

【设置文件筛选条件】

文件筛选条件用于确定在文件搜索的过程中,需要往“文件处理列表”中添加哪些文件。你可以选择将某个目录下的所有文本文件添加到文件处理列表中,也可以选择所有以单词win开头的文件。GULU默认提供三种筛选条件:文件名筛选、文件大小筛选和文件类型筛选。GULU允许开发人员对文件筛选条件进行开发定制。

 

【添加文件与文件批处理】

打开“文件处理列表”后,将左方的文件系统目录结构拖拽到处理列表即可。GULU会自动添加所选目录中的所有文件。所添加的文件都是符合选定的文件筛选条件的。

 

右边的GULU管理器中,按类型对GULU作了分类,在文件被添加到“文件处理列表”后,单击这里的任何一个GULU,即可执行相应的批处理操作,使用非常方便。这里的批处理操作(也就是所谓的GULU)也是可以定制和扩展的。

 

【内嵌脚本】

为了满足实时的客户化批处理操作,对C#/VB.NET有一定了解的用户可以很方便地在GULU中创建文件批处理的脚本,从而省去了需要使用Visual Studio进行二次开发的麻烦。

用户可以根据自己对C#/VB.NET语言的偏好,设置自己熟悉的脚本预言用以编写批处理逻辑:

脚本管理器:

执行结果显示窗口:

编译结果显示窗口:

 

【动态帮助】

GULU文件批处理系统还能根据批处理功能(GULU)的元数据,动态生成帮助信息并以专业的样式予以显示,使得开发人员不必对各个批处理功能重复编写用户使用手册。

 

 

【多语言支持】

GULU的架构支持多国语言。目前只支持中文和英文。用户不需要对其进行设定,GULU会根据Windows操作系统的“区域设置”来决定使用哪种预言。下面是采用了“中文-中国”区域设置后的GULU界面:

中文帮助信息:

 

【详尽的开发文档】

GULU目前的版本还提供了详尽的Class Library开发文档:

转载至:http://www.cnblogs.com/daxnet/archive/2009/11/22/1686963.html

发表在 C# | 留下评论

在诺基亚S60v3上运行.NET程序

诺基亚手机采用的几乎都是Symbian OS操作系统。而.NET Framework则是Windows平台的专利,因此.NET程序只能运行在使用Windows Mobile系统的手机上。那么Symbian OS是否就没法运行.NET程序了呢?

答案是否定的。RedFiveLabs一直致力于在Symbian OS上实现.NET Runtime,在装有RedFiveLabs .NET Framework的Symbian OS手机上,同样可以运行.NET应用程序。首先你得从他们的官方网站下载一个称为Net60的Runtime安装程序,并安装在你的手机上,然后你就可以使用Visual Studio开发Smart Device程序,并将其复制到手机c:\data\RedFiveLabs\Apps目录下,最后使用Net60 Launcher来装载并运行你的程序。

以前,Net60 Runtime是需要付费的,最近RedFiveLabs提供了一款Net60的Free License,它允许个人免费使用Net60。只要你不用于商业目的,这个Free License对于偏爱移动开发的个人来说,还真是一个不错的选择。

Net60具体如何安装和使用,本文不作过多说明。先让我们一睹为快。

  1. 开发的时候,切记一定要选择Windows Mobile 5.0 Smartphone SDK,同时要把.NET Framework的版本降低到2.0。因为到目前为止,RedFiveLabs Net60 Runtime只支持.NET Framework 2.0
  2. 下图是某应用程序在模拟器里运行的效果:
  3. 下图是该应用程序在我的诺基亚N6120c中运行的效果:

RedFiveLabs还特地为手机应用开发了一套SDK,从而使得在Net60下运行的.NET应用程序可以非常方便地操作并调用SMS、蓝牙、红外、Web Service、GPS等功能。

转载至:http://www.cnblogs.com/daxnet/archive/2009/11/29/1686962.html

发表在 C# | 留下评论

C#基础:dynamic类型

dynamic类型是.NET 4.0引入的一个新的概念,它的目的是增强与python等动态语言的互操作性。由于动态的原因,使得dynamic类型的变量只有在运行时才能被确定具体类型,而编译器也会绕过对这种类型的语法检查。其实这样做是危险的。

下面的代码演示了在C#中如何使用dynamic类型:

class Person
{
    public string Name { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        dynamic c = new Person();
        c.Name = "abc";
        c.Age = 30;
        Console.WriteLine(c.Name);
    }
}

这段代码是可以编译通过的,尽管Person类中并没有定义Age这个公有属性,因为c的类型是在运行时动态绑定的,因此,执行这段代码时就会出现如下的错误:

由于编译器会忽略由dynamic定义的变量,因此它能够用来解决一些与强类型“冲突”的问题。不知您是否还记得我之前写的一篇关于泛型的接口抽象的文章。那么,我们可以使用dynamic变量来解决这篇文章中提出的问题。代码如下:

public interface IXRefData
{
    // TODO: add definition here         
}

public class XRefComponent<TData>
    where TData : IXRefData
{
    public void Update() { }
}

public class XRefPickData : IXRefData { }

public class XRefPackData : IXRefData { }

public class XRefUpdator
{
    private List<dynamic> components = new List<dynamic>();
    public List<dynamic> Components
    {
        get { return components; }
        set { components = value; }
    }
}

class Program
{
    static void Main(string[] args)
    {
        XRefUpdator updator = new XRefUpdator();
        
        // 现在就可以将两种完全不同的类型添加到Component列表中了
         updator.Components.Add(
            new XRefComponent<XRefPickData>());

        updator.Components.Add(
            new XRefComponent<XRefPackData>());
    }
}

最后提一句,使用dynamic的程序最好做一下单体测试,因为它可能带来更多的运行时错误。

转载至:http://www.cnblogs.com/daxnet/archive/2010/01/08/1686958.html

发表在 C# | 留下评论

XML的序列化读取方式

在CSDN上,很多朋友会问,C#中如何读取XML文件?如何读取最快?我的回答是,使用XmlTextReader读取的速度是最快的。但也不乏一些效率稍低但也非常有效的读取方式,比如序列化方式。这里详细介绍一下步骤吧。

  1. 使用Visual Studio的CreateSchema工具,或者XMLSpy等第三方的工具,打开需要读取的XML文件,并创建XSD(推荐使用Visual Studio自带的工具)
    比如我有个文件是students.xml,内容如下:
     

    <?xml version="1.0" encoding="utf-8"?>
    <students>
      <student name="Sunny Chen" age="29">
        <registered>true</registered>
      </student>
      <student name="Kitty Wang" age="28">
        <registered>false</registered>
      </student>
    </students>
    

    在使用工具生成xsd以后,获得students.xsd内容如下:

  2. <?xml version="1.0" encoding="utf-8"?>
    <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
      <xs:element name="students">
        <xs:complexType>
          <xs:sequence>
            <xs:element maxOccurs="unbounded" name="student">
              <xs:complexType>
                <xs:sequence>
                  <xs:element name="registered" type="xs:boolean" />
                </xs:sequence>
                <xs:attribute name="name" type="xs:string" use="required" />
                <xs:attribute name="age" type="xs:unsignedByte" use="required" />
              </xs:complexType>
            </xs:element>
          </xs:sequence>
        </xs:complexType>
      </xs:element>
    </xs:schema>
  3. 使用xsd.exe生成类文件,如下:
  4. 将生成的students.cs文件添加到你的项目中。此时如果你的xsd文件和这个cs文件在相同目录下,那么在你的“解决方案资源管理器”中,会将两者关联起来
  5. 使用下面的代码读取你的students.xml文件:
  6. using System;
    using System.IO;
    using System.Xml.Serialization;
    
    namespace XmlRead
    {
        class Program
        {
            static void Main(string[] args)
            {
                XmlSerializer serializer = new XmlSerializer(typeof(students));
                using (FileStream fs = new FileStream(@"c:\students.xml", FileMode.Open))
                {
                    students studs = (students)serializer.Deserialize(fs);
                    foreach (var stud in studs.student)
                    {
                        Console.WriteLine("{0}: Age = {1}, Registered = {2}", 
                            stud.name, 
                            stud.age, 
                            stud.registered);
                    }
                    fs.Close();
                }
            }
        }
    }
  7. 获得的结果如下:

转载至:http://www.cnblogs.com/daxnet/archive/2010/01/13/1686957.html

发表在 C# | 留下评论

在X++中编译并执行C#脚本

发生了什么?

这是件非常有趣的事情。我们现在可以在X++中编译并执行C#脚本。请看下面的X++代码:

static void runCSharp(Args _args)
{
    System.Collections.ArrayList                        scriptArgs;
    System.Collections.ArrayList                        returns;
    System.Collections.IEnumerator                      enumerator;
    System.Collections.Specialized.StringCollection     errors;
    SunnyChen.CSharpScript.ScriptRunner                 runner;
    int                                                 result;

    #localmacro.SourceScript
        "using System;" +
        "using System.Collections;" +
        "public class Script" +
        "{" +
        "    public static ArrayList EntryMethod(ArrayList args)" +
        "    {" +
        "        int a = (int)args[0];" +
        "        int b = (int)args[1];" +
        "        int c = a + b;\r\n" +
        "        ArrayList returns = new ArrayList();" +
        "        returns.Add(c);" +
        "        return returns;" +
        "    }" +
        "}"
    #endmacro

    ;
    runner = new SunnyChen.CSharpScript.ScriptRunner();
    // Prepares the parameters
    scriptArgs = new System.Collections.ArrayList();
    scriptArgs.Add(10);
    scriptArgs.Add(20);

    // Runs the script
    if (runner.Compile(#SourceScript))
    {
        // Gets the return values and output
        returns = runner.Run(scriptArgs);
        result  = returns.get_Item(0);
        info(int2str(result));
    }
    else
    {
        errors = runner.get_CompileErros();
        enumerator = errors.GetEnumerator();
        while (enumerator.MoveNext())
        {
            error(enumerator.get_Current());
        }
    }

}

 

执行完上面这段job程序,Dynamics AX就会编译#SourceScript宏中定义好的C#脚本,然后执行脚本程序,并弹出了一个infolog,上面显示了计算结果:30。如下:

这是如何实现的?

如果你直接将上面的job导入到AOT里去执行,那么在编译阶段就会出错,原因是你的Dynamics AX根本无法找到SunnyChen.CSharpScript.ScriptRunner这一类型。这个类型是我自己编写的一个.NET的类,它使用了.NET提供的CodeDom(Code Document Object Model)和Reflection的机制实现C#脚本的编译和执行。我使用Visual Studio创建了一个名为SunnyChen.CSharpScript的Class Library项目,然后往该项目中添加了这个类:

public class ScriptRunner
{
    private Assembly assembly;
    private StringCollection compileErrors = new StringCollection();

    public bool Compile(string script)
    {
        try
        {
            CSharpCodeProvider codeProvider = new CSharpCodeProvider();
            CompilerParameters compilerParameters = new CompilerParameters();
            compilerParameters.GenerateExecutable = false;
            compilerParameters.GenerateInMemory = true;
            compilerParameters.IncludeDebugInformation = false;
            compilerParameters.ReferencedAssemblies.Add("System.dll");
            compilerParameters.ReferencedAssemblies.Add("System.Windows.Forms.dll");
            compilerParameters.ReferencedAssemblies.Add("System.XML.dll");
            CompilerResults results = 
                codeProvider.CompileAssemblyFromSource(compilerParameters, script);

            if (!results.Errors.HasErrors)
            {
                this.assembly = results.CompiledAssembly;
                return true;
            }
            else
            {
                this.compileErrors.Clear();
                foreach (CompilerError ce in results.Errors)
                {
                    compileErrors.Add(ce.ErrorText);
                }
                return false;
            }
        }
        catch (Exception e)
        {
            EventLog.WriteEntry("CSharpScript", 
                e.Message, EventLogEntryType.Error); 
            throw;
        }
    }

    public StringCollection CompileErros
    {
        get
        {
            return compileErrors;
        }
    }

    public ArrayList Run(ArrayList args)
    {
        ArrayList ret = new ArrayList();
        try
        {
            Type entryType = null;
            MethodInfo entryPoint = null;
            foreach (var type in assembly.GetExportedTypes())
            {
                if (type.Name.Equals("Script"))
                {
                    entryType = type;
                    break;
                }
            }
            foreach (var methodInfo in entryType.GetMethods(BindingFlags.Public | 
                BindingFlags.Static))
            {
                if (methodInfo.Name.Equals("EntryMethod"))
                {
                    entryPoint = methodInfo;
                    break;
                }
            }
            Type returnType = entryPoint.ReturnType;
            ParameterInfo[] parameters = entryPoint.GetParameters();
            if (returnType.Equals(typeof(ArrayList)) &&
                parameters != null &&
                parameters.Count() == 1 &&
                parameters[0].ParameterType.Equals(typeof(ArrayList)))
            {
                ret = (ArrayList)entryPoint.Invoke(null, new object[] { args });
            }

        }
        catch(Exception e)
        {
            StringBuilder sb = new StringBuilder();
            
            sb.Append(e.Message);
            sb.Append("-->");
            sb.Append(e.StackTrace);
            sb.Append(Environment.NewLine);
            Exception inner = e.InnerException;
            while (inner != null)
            {
                sb.Append(inner.Message);
                sb.Append("-->");
                sb.Append(inner.StackTrace);
                sb.Append(Environment.NewLine);
                inner = inner.InnerException;
            }
            EventLog.WriteEntry("CSharpScript", sb.ToString(), EventLogEntryType.Error);
            throw;
        }

        return ret;
    }
}

 

在编译选项中,需要对SunnyChen.CSharpScript项目进行数字签名:

签名完,编译好以后,就将编译出来的SunnyChen.CSharpScript.dll安装到GAC里。方法是,在Run里输入c:\windows\assembly,然后将这个dll拖拽到打开的窗口中即可。

最后一步,将SunnyChen.CSharScript添加到Dynamics AX的AOT\References中,再回过头来编译上面的job。此时job能够顺利通过编译并正确执行。

这样做有什么实际应用?

看上去这只是一个噱头,就是一种技术把戏,貌似在实际应用中没什么特别的用处。其实,这个技术可以帮我们解决这样的场景:比如产品销售时,优惠策略的动态配置。针对不同的产品、分类或者地域,产品价格优惠的计算是非常复杂的,通常情况下我们也只能在系统设计的时候引入常见的几种促销优惠方式,或者更灵活一点,提供接口,便于今后二次开发进行扩展。然而这两种方式都是需要软件开发人员介入的,而不是面向最终用户的。当用户打算修改优惠策略时,就不得不去做系统扩展,甚至改动现有的逻辑。

我做了一个原型,就是根据CustTable中所有客户的成交交易总额进行折扣计算。比如当用户交易额超过2000元,则给8.7折优惠,如果没有超过,则给9.5折优惠,从而在Base Amount的基础上,计算出折扣金额和最后应付款额。请看:

在上面的Form中,我定义了折扣计算的代码,这个代码可以包含基于result(折扣额)和totalAmount(总交易额)计算的C#代码。代码可以包含任何C#支持的语法。当然,最终用户对编程肯定是一窍不通的,不过在这里输入几个if…else或者switch…case,应该还是问题不大的。下图就是运行时的结果:

源代码

本文提供SunnyChen.CSharpScript的源码下载。执行C#的job我就不提供了,读者自己从上文中拷贝即可。

点击下载此文件

转载至:http://www.cnblogs.com/daxnet/archive/2010/01/20/1686953.html

发表在 C# | 留下评论

在非UI线程中改变UI控件属性的通用方法

在.NET中如需在非UI线程中改变UI控件属性时,CLR会抛出异常,提示无法在非UI线程中更新界面上的控件(Cross-thread operation not valid)。一般情况下有两种解决办法。第一种就是设置Control的静态属性CheckForIllegalCrossThreadCalls为False,如下:

public Form1()
{
    InitializeComponent();
    Control.CheckForIllegalCrossThreadCalls = false;
}

private void button1_Click(object sender, EventArgs e)
{
    Thread thread = new Thread(() =>
    {
        for (int i = 0; i < 100000; i++)
        {
            label1.Text = i.ToString();
            label1.Refresh();
        }
    });
    thread.Start();
}

另一种办法,就是使用委托,根据控件的InvokeRequired属性判断当前控件的更新操作是否是在另一个线程中。如果是,则使用委托进行方法调用并更新控件。但是这种方法有个缺点,就是需要针对每个控件的属性设置方式创建一些单独的委托和方法,这些委托和方法仅仅是在解决跨线程操作的时候使用。比如,你在另一个线程中需要修改某个label的text时,你就需要创建一个SetLabelText方法,假设你还需要更新TextBox的text,那么你还需要另外创建一个SetTextBoxText方法。

通过下面的委托和方法的定义,我们实现了“一次定义,多次使用”。请看:

private delegate void ParameterizedControlUpdate(params object[] args);

private delegate void ControlUpdateDelegate(Component c,
    ParameterizedControlUpdate callback,
    params object[] args);

private void DelegatedControlUpdate(Component c,
    ParameterizedControlUpdate callback,
    params object[] args)
{
    Control target = (c is Control) ? (c as Control) : this;
    if (target.InvokeRequired)
    {
        ControlUpdateDelegate d = DelegatedControlUpdate;
        target.Invoke(d, new object[] { c, callback, args });
    }
    else
    {
        callback(args);
    }
}

于是,上面的例子可以改为:

private void button1_Click(object sender, EventArgs e)
{
    Thread thread = new Thread(() =>
    {
        for (int i = 0; i < 100000; i++)
        {
            DelegatedControlUpdate(label1, args =>
                {
                    label1.Text = (string)args[0];
                    label1.Refresh();
                }, i.ToString());
        }
    });
    thread.Start();
}

转载至:http://www.cnblogs.com/daxnet/archive/2010/01/30/1686950.html

发表在 C# | 留下评论

AspDotNetStorefront中事件处理页面开发的注意事项

最近在研究Dynamics AX与AspDotNetStorefront的集成问题。AspDotNetStorefront提供一种领域事件模型,允许开发人员对事件进行定制或捕获,进而执行一些客户化操作。它所支持的EventHandler是一系列的ASP.NET页面,也就是CalloutURL中需要指定一个ASP.NET页面作为Event Handler。

在第一次开发的时候,我一直得到“URL authorization failed for the request.”的错误,这个错误信息可以在Windows的事件查看器中找到。几经波折,最后发现,原来是在我的callout page的aspx代码中,少加了一个ValidateRequest选项:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="CalloutTest.aspx.cs" 
    Inherits="CalloutTest" ValidateRequest="false" %>

转载至:http://www.cnblogs.com/daxnet/archive/2010/02/04/1686948.html

发表在 C# | 留下评论

Visual Studio 2010 RC版发布

Microsoft向公众发布了Visual Studio 2010 with .NET 4.0, Release Candidate版本。详细信息请参见:http://msdn.microsoft.com/en-gb/vstudio/dd582936.aspx。

转载至:http://www.cnblogs.com/daxnet/archive/2010/02/11/1686946.html

发表在 C# | 留下评论

C#中动态订阅控件中任意事件的方法

这个题目想了半天,不太好用一句话描述。这样,举个简单的应用场景:在用Windows Forms制作向导程序的时候,通常会有“上一步”、“下一步”这样的按钮。假设现在需要做一个通用的“向导制作框架”,那么我们就需要在这个“向导制作框架”中,对“上一步”、“下一步”这些按钮是否可用(是否Enabled)进行控制。而控制条件是由开发人员在实际使用“向导制作框架”进行开发时确定的。比如:只有在当前向导页面的某个文本框里被输入了字符串以后,“下一步”才可用;或者只有在某个按钮被按下的时候,“下一步”才可用。于是,我们的“向导制作框架”要能够允许开发人员来确定,当触发什么事情的时候,“下一步”才可用。

 

我们先从特例入手,假设向导页面上只有一个按钮叫btn,只有点击btn以后,“下一步”才能够被点击。编程上很容易实现这个效果:直接在窗体上订阅btn的Click事件,在Click事件里,写下“btnNext.Enabled = true;”这样一句话。

 

现在问题来了:我们需要为开发人员提供一个“向导制作框架”,也就是说,这个框架根本无法预测开发人员需要订阅哪些控件的哪些事件,只能留出一个接口,让开发人员自己调用这个接口实现事件的动态注册。Windows Forms提供的控件类型多种多样,而且不同的事件有着不同的函数签名(也就是委托,比如Click事件和MouseDown事件就是用的两个不同的委托),如何让我们的框架能够支持任意的控件,并在任意控件的任意事件发生时,调用“btnNext.Enabled = true;”这条语句,使得“下一步”按钮可用呢?

 

要实现这样的功能,我们需要用到反射。首先,定义一个泛型方法,在这个方法里,我们直接对btnNext进行设置,如下:

 

protected void DoTrigger<T>(object sender, T eventArgs)
    where T : System.EventArgs
{
    this.btnNext.Enabled = true;
}

然后,根据用户给定的控件实例和事件名称,获得EventInfo对象。这个EventInfo里有个重要的属性,就是EventHandlerType,它就是定义event所使用的委托类型。为了使开发人员指定的事件能够绑定到上面的DoTrigger函数上,我们需要知道那个EventArgs的具体类型,下面的代码可以将某个委托类型的所有参数类型全部读取出来:

 

 

private Type[] GetDelegateParameterTypes(Type d)
{
    if (d.BaseType != typeof(MulticastDelegate))
    {
        throw new InvalidOperationException("Not a delegate.");
    }

    MethodInfo invoke = d.GetMethod("Invoke");
    if (invoke == null)
    {
        throw new InvalidOperationException("Not a delegate.");
    }

    ParameterInfo[] parameters = invoke.GetParameters();
    Type[] typeParameters = new Type[parameters.Length];
    for (int i = 0; i < parameters.Length; i++)
    {
        typeParameters[i] = parameters[i].ParameterType;
    }

    return typeParameters;
}

 

注意:如果是标准的事件委托,一般情况下都是第一个参数为object类型,第二个参数为EventArgs绑定类型,无返回值的签名格式。换句话说,一般情况下,上面的这段代码返回的数组包含两个对象:object和一个继承于EventArgs(或者是EventArgs本身)的类型。在这里,我们取数组里的第二个成员。

好了,现在通过反射,获得DoTrigger方法的MethodInfo,并通过MethodInfo.MakeGenericMethod方法,将上一步获得的EventArgs类型绑定到DoTrigger方法上,并使用Delegate.CreateDelegate生成Event Handler:

private Delegate GetEventHandler(Control control, EventInfo eventInfo)
{
    try
    {
        if (eventInfo == null)
            throw new Exception(string.Format("Unable to find an event named '{0}' on the control '{1}'.",
                eventInfo.Name, control));
        Type[] delegateParameters = this.GetDelegateParameterTypes(eventInfo.EventHandlerType);
        if (delegateParameters == null ||
            delegateParameters.Length != 2)
            throw new InvalidOperationException(string.Format("Event '{0}' is not valid.", eventInfo.Name));
        Type eventArgsType = delegateParameters[1];

        MethodInfo doEventMethod = this.GetType().GetMethod("DoTrigger", 
            BindingFlags.NonPublic | BindingFlags.Instance);

        if (doEventMethod == null)
            throw new Exception("DoTrigger method doesn't exist.");
        if (!doEventMethod.IsGenericMethod)
            throw new Exception("DoTrigger method is not a generic method.");
        MethodInfo concreteDoEventMethod = doEventMethod.MakeGenericMethod(eventArgsType);
        Delegate d = Delegate.CreateDelegate(eventInfo.EventHandlerType, this, concreteDoEventMethod);
        return d;
    }
    catch
    {
        throw;
    }
}

通过向上面的方法传入一个任意控件和该控件中任意事件的Method Info,即可获得处理该事件的Event Handler,也就是由DoTrigger泛型方法来处理该指定的事件:

public void RegisterTrigger(Control control, string eventName)
{
    try
    {
        EventInfo eventInfo = control.GetType().GetEvent(eventName, 
            BindingFlags.Public | BindingFlags.Instance);

        Delegate d = this.GetEventHandler(control, eventInfo);
        eventInfo.AddEventHandler(control, d);
    }
    catch
    {
        throw;
    }
}

最后,在使用的时候,代码就简单啦:

private void Form_Load (object sender, System.EventArgs e)
{
    this.RegisterTrigger (btn, "Click");
    this.RegisterTrigger (textBox1, "TextChanged");
    this.RegisterTrigger (textBox2, "MouseDown");
}

现在,不管是btn被单击,还是textBox1里的文字被更改,还是在textBox2鼠标按钮被按下,都会直接触发DoTrigger函数,进而使得“下一步”按钮变得可用(Enabled为true)。

转载至:http://www.cnblogs.com/daxnet/archive/2010/03/19/1689838.html

发表在 C# | 留下评论