我们经常会有这样的需求或者想法:动态的生成或者修改代码。当然,我们可以把代码看成字符串而直接修改,但是这种做法也未免太生硬了,能解决的问题有限;而另一个方式就是CodeDom。
CodeDom是强大的!我们感谢微软,给我们提供了强大的描述面向对象语言的框架;我们感谢微软,给我们提供了能够根据CodeDom生成代码或者程序集的CodeDomProvider;可惜微软没有给我们提供能够从object或者代码生成CodeDom的能力。
关于CodeDom的知识本文不过多涉及、感兴趣的童鞋可以阅读MSDN或者博客园的其它文章学习了解。本系列期望解决的问题就是如何将对象或者代码生成CodeDom。当然,由于微软并没有提供这样的支持,而我也不可能写一个解析C#语言或者VB语言的CodeParser,所以本文提供的方案也能力有限,但愿能够解决你的一部分问题或者给您能学到点知识。
这是本系列的第一篇文章,如何让一个Component对象生成CodeDom。核心思想就是虚拟一个DesignTime的环境,并将Component添加到Designer中,然后使用ComponentTypeCodeDomSerializer将Component序列化成CodeTypeDeclaration。本方案可以在任意程序下执行,不依赖IDE,也不需要引用各种奇怪的dll。
下面就是具体实现:
首先,创建一个WindowsControlLibrary,名叫WindowsControlLibrary1。
然后,添加一个类取名MyComponent1,类中有一个GetSet的属性IntProperty,还有一个设置了背景色的TextBox:
01 |
public class MyComponent1 : Component |
02 |
{ |
03 |
public MyComponent1() |
04 |
{ |
05 |
textBox1 = new TextBox(); |
06 |
textBox1.BackColor = Color.Red; |
07 |
} |
08 |
|
09 |
private int int1; |
10 |
private TextBox textBox1; |
11 |
|
12 |
public int IntProperty |
13 |
{ |
14 |
get { return int1; } |
15 |
set { int1 = value; } |
16 |
} |
17 |
|
18 |
public TextBox TextBoxProperty |
19 |
{ |
20 |
get { return textBox1; } |
21 |
} |
22 |
} |
接着创建另一个WindowsFormsApplication项目:CodeDomSample,并引用System.Design和WindowsControlLibrary1项目(当然,你也可以把WindowsControlLibrary1编译成dll并引用这个dll)
现在,创建我们的核心类CodeTypeConverter,对于具体实现我不做过多的说明,你不必要关心实现的具体细节,只要这个实现能够满足你的需求就行了。如果你有看不明白的地方请提问,我会认真回答。
01 |
public class CodeTypeConverter |
02 |
{ |
03 |
private IServiceProvider _serviceProvider; |
04 |
|
05 |
private IDesignerHost DesignerHost |
06 |
{ |
07 |
get |
08 |
{ |
09 |
return this ._serviceProvider.GetService( typeof (IDesignerHost)) as IDesignerHost; |
10 |
} |
11 |
} |
12 |
|
13 |
//将Component Load到DesignerHost中并返回 |
14 |
private IComponent LoadComponent(IComponent component) |
15 |
{ |
16 |
DesignSurfaceManager manager = new DesignSurfaceManager(); |
17 |
DesignSurface surface = manager.CreateDesignSurface(); |
18 |
surface.BeginLoad(component.GetType()); |
19 |
this ._serviceProvider = surface; |
20 |
IComponent newComponent = DesignerHost.RootComponent; |
21 |
//暴力克隆,将component上的所有Field设到newComponent上 |
22 |
FieldInfo[] fields = component.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance); |
23 |
foreach (FieldInfo field in fields) |
24 |
{ |
25 |
object fieldValue = field.GetValue(component); |
26 |
//将所有子Component Load到DesignerHost中 |
27 |
if (fieldValue != null && fieldValue is IComponent) |
28 |
{ |
29 |
DesignerHost.Container.Add(fieldValue as IComponent, field.Name); |
30 |
} |
31 |
field.SetValue(newComponent, fieldValue); |
32 |
} |
33 |
return newComponent; |
34 |
} |
35 |
|
36 |
//将DesignerHost中的Component转成CodeType |
37 |
public CodeTypeDeclaration ConvertComponentToCodeType(IComponent component) |
38 |
{ |
39 |
component = this .LoadComponent(component) as Component; |
40 |
DesignerSerializationManager manager = new DesignerSerializationManager( this ._serviceProvider); |
41 |
//这句Code是必须的,必须要有一个session,DesignerSerializationManager才能工作 |
42 |
IDisposable session = manager.CreateSession(); |
43 |
TypeCodeDomSerializer serializer = manager.GetSerializer(component.GetType(), typeof (TypeCodeDomSerializer)) as TypeCodeDomSerializer; |
44 |
List< object > list = new List< object >(); |
45 |
foreach (IComponent item in this .DesignerHost.Container.Components) |
46 |
{ |
47 |
list.Add(item); |
48 |
} |
49 |
CodeTypeDeclaration declaration = serializer.Serialize(manager, component, list); |
50 |
session.Dispose(); |
51 |
return declaration; |
52 |
} |
53 |
} |
好了,CodeTypeConverter实现完成。现在在Form1中写一个Test方法测试:
01 |
public Form1() |
02 |
{ |
03 |
InitializeComponent(); |
04 |
Test(); |
05 |
} |
06 |
07 |
public void Test() |
08 |
{ |
09 |
CodeTypeConverter designerHost = new CodeTypeConverter(); |
10 |
MyComponent1 component = new MyComponent1(); |
11 |
component.IntProperty = 10; |
12 |
component.TextBoxProperty.Text = "Hello World" ; |
13 |
14 |
CodeTypeDeclaration componentType = designerHost.ConvertComponentToCodeType(component); |
15 |
componentType.Name = component.GetType().Name + "1" ; |
16 |
17 |
StringBuilder bulder = new StringBuilder(); |
18 |
StringWriter writer = new StringWriter(bulder, CultureInfo.InvariantCulture); |
19 |
CodeGeneratorOptions option = new CodeGeneratorOptions(); |
20 |
option.BracingStyle = "C" ; |
21 |
option.BlankLinesBetweenMembers = false ; |
22 |
CSharpCodeProvider codeDomProvider = new CSharpCodeProvider(); |
23 |
codeDomProvider.GenerateCodeFromType(componentType, writer, option); |
24 |
Debug.WriteLine(bulder.ToString()); |
25 |
writer.Close(); |
26 |
} |
CodeDomSample跑起来以后,就可以在输出窗口看到如下的输出:
01 |
public class MyComponent11 : WindowsControlLibrary1.MyComponent1 |
02 |
{ |
03 |
private System.Windows.Forms.TextBox textBox1; |
04 |
private MyComponent11() |
05 |
{ |
06 |
this .InitializeComponent(); |
07 |
} |
08 |
private void InitializeComponent() |
09 |
{ |
10 |
this .textBox1 = new System.Windows.Forms.TextBox(); |
11 |
// |
12 |
// textBox1 |
13 |
// |
14 |
this .textBox1.BackColor = System.Drawing.Color.Red; |
15 |
this .textBox1.Location = new System.Drawing.Point(0, 0); |
16 |
this .textBox1.Name = "textBox1" ; |
17 |
this .textBox1.Size = new System.Drawing.Size(100, 20); |
18 |
this .textBox1.TabIndex = 0; |
19 |
this .textBox1.Text = "Hello World" ; |
20 |
// |
21 |
// |
22 |
// |
23 |
this .IntProperty = 10; |
24 |
} |
25 |
} |