使用CodeDom動態生成類型


.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();
        }

參考

MSDN CodeDom


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM