.NET 3.5的時候加入了匿名類型這個特性,我們可以直接使用 new {name="abc"} 來直接生成一個對象。這個特性現在應用的地方很多,比如dapper的查詢參數都是用匿名對象。
其實匿名對象也不是真的沒有名稱,編譯器在編譯后自動會生成一個Type。我們看看IL就知道了。
編譯器會自動生成一個叫做<>f__AnonymousType0`1的類型。
動態生成類型
但是有的時候我們可能類型里面的字段都是不確定的,這個時候我們就需要去動態生成一個類型了。
- 動態生成類型第一個想到的就是反射,但是仔細想想反射都是基於現有Type的基礎上完成的,咱們現在連Type都沒有,所以這條路不通。
- 第二個dynamic,dynamic確實是個好辦法,可以動態指定字段的名稱,但是有的三方的庫不支持比如dapper。
- 最后CodeDom,CodeDom可以在運行時直接生成一個Type。CodeDom生成Type主要分成3步。
比如我們要生成一個Person類:
public class Person
{
public string name;
public ing age;
public Person(string name ,int age)
{
this.name = name;
this.age = age;
}
}
構造類型
private string _ns = "__x";
private string _className;
private Dictionary<Type, string> _fieldsDictionary;
private string _sourceCode;
private CodeCompileUnit _targetUnit;
private CodeTypeDeclaration _targetClass;
public SourceCodeCreater(string className,Dictionary<Type,string> fieldsDictionary )
{
_fieldsDictionary = fieldsDictionary;
_className = className;
_targetUnit = new CodeCompileUnit();
CodeNamespace ns = new CodeNamespace(_ns);
ns.Imports.Add(new CodeNamespaceImport("System"));
_targetClass = new CodeTypeDeclaration(className);
_targetClass.IsClass = true;
_targetClass.TypeAttributes =
TypeAttributes.Public | TypeAttributes.Sealed;
ns.Types.Add(_targetClass);
_targetUnit.Namespaces.Add(ns);
}
public string SourceCode
{
get { return _sourceCode; }
}
public string TypeName
{
get
{
return string.Format("{0}.{1}", _ns, _className);
}
}
private void AddFields()
{
// Declare fields .
foreach (var kv in _fieldsDictionary)
{
CodeMemberField widthValueField = new CodeMemberField();
widthValueField.Attributes = MemberAttributes.Public;
widthValueField.Name = kv.Value;
widthValueField.Type = new CodeTypeReference(kv.Key);
_targetClass.Members.Add(widthValueField);
}
}
private void AddCtor()
{
// Declare constructor
CodeConstructor constructor = new CodeConstructor();
constructor.Attributes =
MemberAttributes.Public | MemberAttributes.Final;
// Add parameters.
foreach (var kv in _fieldsDictionary)
{
constructor.Parameters.Add(new CodeParameterDeclarationExpression(
kv.Key, kv.Value));
}
// Add field initialization logic
foreach (var kv in _fieldsDictionary)
{
CodeFieldReferenceExpression reference =
new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(), kv.Value);
constructor.Statements.Add(new CodeAssignStatement(reference,
new CodeArgumentReferenceExpression(kv.Value)));
}
_targetClass.Members.Add(constructor);
}
我們按照手寫類的結構添加字段跟構造函數。
生成CSharp代碼
public string Create()
{
AddFields();
AddCtor();
CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
CodeGeneratorOptions options = new CodeGeneratorOptions();
options.BracingStyle = "C";
using (StringWriter sourceWriter = new StringWriter())
{
provider.GenerateCodeFromCompileUnit(
_targetUnit, sourceWriter, options);
_sourceCode = sourceWriter.ToString();
}
return _sourceCode;
}
生成CSharp代碼
編譯
SourceCodeCreater sourceCodeCreater =new SourceCodeCreater(className,fields);
var sourceCode = sourceCodeCreater.Create();
Console.WriteLine(sourceCode);
var typeName = sourceCodeCreater.TypeName;
CSharpCodeProvider p = new CSharpCodeProvider();
CompilerParameters param = new CompilerParameters(new string[] { "System.dll" });
CompilerResults rel = p.CompileAssemblyFromSource(param, sourceCode);
Type t = rel.CompiledAssembly.GetType(typeName);
編譯代碼獲得Type
運行一下
static void Main(string[] args)
{
var className = "Person";
var fields =new Dictionary<Type,string>();
fields.Add(typeof(string),"name");
fields.Add(typeof(int),"age");
SourceCodeCreater sourceCodeCreater =new SourceCodeCreater(className,fields);
var sourceCode = sourceCodeCreater.Create();
Console.WriteLine(sourceCode);
var typeName = sourceCodeCreater.TypeName;
CSharpCodeProvider p = new CSharpCodeProvider();
CompilerParameters param = new CompilerParameters(new string[] { "System.dll" });
CompilerResults rel = p.CompileAssemblyFromSource(param, sourceCode);
Type t = rel.CompiledAssembly.GetType(typeName);
Console.WriteLine(t.FullName);
foreach (var f in t.GetFields())
{
Console.WriteLine("Type:{0} Name:{1}",f.FieldType,f.Name);
}
Console.Read();
}