用户代码序列化:CodeDom的力量

来源:互联网 发布:天津商业大学网络管理 编辑:程序博客网 时间:2024/04/28 05:42


   VS.NET中的主要对象持续性机制是通过直接的代码发送来处理的。在所有Web和Windows Forms包含的InitializeComponent方法中,你已经看到了这一现象。在Component-继承类型中也显示了这一过程。两个属性决定了这一行为:System.ComponentModel.Design.Serialization.RootDesignerSerializerAttribute以及System.ComponentModel.Design.Serialization.DesignerSerializerAttribute.,正如在开始讨论到的DesignerAttribute,在根和标准串化器之间有一个明显的区别。但是不像DesignerAttribute,总是利用标准(非-根)串化器,当构件同时是被设计的根构件时,要额外利用根串化器。通常只是定制非-根串化器。此非-根串化器的指示器就是:IComponent接口已经包含根串化器。

[RootDesignerSerializer(typeof(RootCodeDomSerializer), typeof(CodeDomSerializer))] 
public interface IComponent

   但是它没有提供设计常规串化器的属性。但是,IComponent的特殊实现提供这个属性。例如:

[DesignerSerializer(typeof(Microsoft.VSDesigner.WebForms.ControlCodeDomSerializer)), 
typeof(CodeDomSerializer))]
public class System.Web.UI.Control : IComponent

   以及

[DesignerSerializer(typeof(System.Windows.Forms.Design.ControlCodeDomSerializer)), 
typeof(CodeDomSerializer))]
public class System.Windows.Forms.Control : Component

   注意:两者都有它们独特的串化器,因为Windows窗体被保存到代码的方法与Web 窗体被保存到代码的方法有很大的区别。前者串行化InitializeComponent方法的所有值和设置,然而后者仅仅储存在代码隐藏事件处理程序连接附件中,因为控件属性被保存在aspx页面中。

   你肯定注意到不管控件是用在VB.NET项目中还是用在一个C#项目(或者是可用于此目的的其它语言)中,InitializeComponent总是用正确的语言发送代码。因为.NET中一个新的功能(此功能被称为CodeDom. )CodeDom是一个类型集,此类型集允许我们输写对象层次结构 ,这些对象层次结构代表更加的普通语言构造,例如:类型,领域以及属性宣告,事件连接附件,try..catch..finally块等等。它们允许我们创建一个所谓的预期目标代码的abstract syntax tree (AST)。abstract syntax tree (AST)是抽象的,它不代表VB.NET或者C#代码,但是代表constructs它们自己。

    串化器传递到集成开发环境的东西就是包含代码的ASTs,这正是它们期望保存的。集成开发环境依次创建一个System.CodeDom.Compiler.CodeDomProvider -继承类型,System.CodeDom.Compiler.CodeDomProvider -继承类型与当前项目完全匹配,例如:Microsoft.CSharp.CSharpCodeProvider 或者Microsoft.VisualBasic.VBCodeProvider.此对象最后负责以具体语言代码传输AST,而这些具体语言代码早已被嵌入到了InitializeComponent方法中。

    CodeDom并不是非常复杂,让我们来迅速地学习一下CodeDom。

CodeDom句法

    最好的方法是通过例子来学习CodeDom,因此来看一下某种C#代码,以及它对等的CodeDom语句(我们假设它们都发生在类型内部)。被下载的代码包括一个项目,以此来检查CodeDomTester文件夹中的CodeDom。它是一个简单的控制器应用程序,在此控制器应用程序上有两个骨架方法:GetMembers和GetStatements。可以把样本CodeDom代码放到这两个方法中,看一下输出的结果。
C#:

private string somefield;

CodeDom:

CodeMemberField field = new CodeMemberField(typeof(string), "somefield");

   所有类型层成员表示法CodeMemberEvent, CodeMemberField, CodeMemberMethod and CodeMemberProperty,都是从CodeTypeMember继承来的,默认地拥有private和final属性。

C#:

public string somefield = "SomeValue";

CodeDom:

CodeMemberField field = new CodeMemberField(typeof(string), "somefield");
field.InitExpression = new CodePrimitiveExpression("SomeValue");
field.Attributes = MemberAttributes.Public;

C#

this.somefield = GetValue();

CodeDom:

CodeFieldReferenceExpression field = new CodeFieldReferenceExpression( 
new CodeThisReferenceExpression(), "somefield");
CodeMethodInvokeExpression getvalue = new CodeMethodInvokeExpression(
new CodeThisReferenceExpression(), "GetValue", new CodeExpression[0]);
CodeAssignStatement assign = new CodeAssignStatement(field, getvalue);

   注意:实际上冗长的程度按指数倍增加。并且注意C#代码中的GetValue()方法对此有一个隐式引用,在CodeDom中必须是显示的。

C#

this.GetValue("Someparameter", this.somefield);

CodeDom

CodeMethodInvokeExpression call = new CodeMethodInvokeExpression(); 
call.Method = new CodeMethodReferenceExpression(
new CodeThisReferenceExpression(), "GetValue");
call.Parameters.Add(new CodePrimitiveExpression("Someparameter"));
CodeFieldReferenceExpression field = new
CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "somefield");
call.Parameters.Add(field);

   我们调用同一方法的一个假定超载。注意首先创建方法调用表示法,然后为它(指向this)指定一个方法引用和一个方法名称。下面附加两个参数,第二个单元是这个领域的引用。

    如果想避免无尽的而且没用的变量声明,不采用临时变量就可以创建语句。这使得代码不那么清晰易读,但是更加紧凑。创建这些语法的一个好技术就是考虑目标代码,从内部向外部生成目标代码。例如在上面的代码中。

this.GetValue("Someparameter", this.somefield);

   首先创建参数,然后考虑方法引用,一旦想做这些工作,写下下面这些东西:

CodeMethodInvokeExpression call = 
new CodeMethodInvokeExpression(new CodeThisReferenceExpression(),
"GetValue",
new CodeExpression[] { new CodePrimitiveExpression("Someparameter"),
new CodeFieldReferenceExpression(new CodeThisReferenceExpression(),
"somefield")});

    我们看到的最后情况是一个this.somefield,然后是原始表示法。这是作为方法调用的参数阵列的初始化表示法而传递的。然后你得到this.somefield,最后this.somefield引发实际调用。

    注意正确的缩排可以提供巨大的帮助,但是大部分工作还是需要你自己完成,尤其是一些附带有巢状层次的工作。为了达到一些合法性,最重要的巢状就是阵列初始化(前面所提到的)。也推荐把预期C# (or VB.NET)输出代码放到多线语句上面,因此每个人都知道你试图发送什么。

    这些是定义所有跨语言功能的类型。但是让我们看一下需要给被扩展属性提供的具体持续性代码。

发送CodeDom

    我们将需要把一个自定义串化器与Base控制器连接起来,为了定制持续性,以及发送代码来保存视图 映射以及我们需要的任何潜在代码。

[DesignerSerializer(typeof(ControllerCodeDomSerializer),
typeof(CodeDomSerializer))]
public class BaseController : Component, IExtenderProvider

    自定义串化器必须从CodeDomSerializer继承而来。这个基本抽象类型(此基本抽象类型位System.ComponentModel.Design序列化)包含必须实现的两个抽象方法。

public abstract class CodeDomSerializer 
{
public abstract object Serialize(
IDesignerSerializationManager manager, object value);
public abstract object Deserialize(
IDesignerSerializationManager manager, object codeObject);
}

    无论什么时候,对象需要被保存时,Serialize方法都会被调用。返回值必须是类型CodeStatementCollection(此类型包括代码来保存)的一个对象。同样的,Deserialize方法中的codeObject参数包括前面被发送的语句。

    在概述中,我们就谈到示例根组件,通过根设计器来设计它。在根组件世界中,几乎所有构件(以及控件)中都会发生这一过程。真正发生的却是集成开发环境 执行InitializeComponent中的大部分代码,重新创建对象,就像它们处在运行库里一样。我们说大部分而不是所有是因为仅仅修改问题中的构件的语句才被调用:例如:它们中的属性设置以及方法调用.通过定制Deserialize方法,我们有机会在设计时期重建过程中互相作用。通常这是没必要的,因此大部分时间我们就把球传到初始构件串化器ComponentCodeDomSerializer,此ComponentCodeDomSerializer基本上实现此代码。为了得到类型的串化器,我们使用我们接受到的IDesignerSerializationManager参数的GetSerializer方法。此对象有其它有用的方法,我们将在后面使用它们。

    因此Deserialize实现通常是这样的:

public override object Deserialize( 
IDesignerSerializationManager manager, object codeObject)
{
CodeDomSerializer serializer =
manager.GetSerializer(typeof(Component), typeof(CodeDomSerializer));
return serializer.Deserialize(manager, codeObject);
}

    重新找回构件初始串化器是管理器的普通使用。因为反序列化通常是一样的。我们将把它放在基本类型,并且将从控制器串化器生成基本类型。

internal abstract class BaseCodeDomSerializer : CodeDomSerializer
{
protected CodeDomSerializer GetBaseComponentSerializer(
IDesignerSerializationManager manager)
{
return (CodeDomSerializer)
manager.GetSerializer(typeof(Component), typeof(CodeDomSerializer));
}

public override object Deserialize(
IDesignerSerializationManager manager, object codeObject)
{
return GetBaseComponentSerializer(manager).Deserialize(manager,
codeObject);
}
}

   后面将为这个类型补充其它的公共方法。现在需要进行串行化过程,我们需要通过Hashtable的所有DictionaryEntry元素来迭代,发送下面的代码,为了保存ConfiguredViews属性。

controller.ConfiguredViews.Add("txtID", 
new ViewInfo("txtID", "Text", "Publisher", "ID"));

   另一个公共的方法就是让初始构件串化器执行它自己的工作。然后补充我们的自定义语句。通过这种方法,我们避免了自己保存公共构件属性。因此串化器开始执行这些任务:

internal class ControllerCodeDomSerializer : BaseCodeDomSerializer 
{
public override object
Serialize(IDesignerSerializationManager manager, object value)
{
CodeDomSerializer serial = GetBaseComponentSerializer(manager);
if (serial == null)
return null;
CodeStatementCollection statements = (CodeStatementCollection)
serial.Serialize(manager, value);
原创粉丝点击