开发者

How to use CodeDOM's CodeMemberProperty to generate DebuggerStepThrough attribute for a property getter or setter

How can I generate a DebuggerStepThroughAttribute over a getter/setter with CodeDOM?

This question follows from MSDN documentation a开发者_如何学JAVAnd a question on StackOverflow.


CodeMemberProperty's CustomAttributes is of type CodeAttributeDeclarationCollection. If an Attribute is specified here, then it's added above the property declaration line: generated code won't compile.

CodeMemberProperty's GetStatements and SetStatements are collections: i cannot specify custom attributes on them.

Here's what I can see in Microsoft CSharpCodeGenerator with help from Reflector:

private void GenerateProperty(CodeMemberProperty e, CodeTypeDeclaration c)
{
    if ((this.IsCurrentClass || this.IsCurrentStruct) || this.IsCurrentInterface)
    {
        if (e.CustomAttributes.Count > 0)
        {
            this.GenerateAttributes(e.CustomAttributes);
        }
        if (!this.IsCurrentInterface)
        {
            if (e.PrivateImplementationType == null)
            {
                this.OutputMemberAccessModifier(e.Attributes);
                this.OutputVTableModifier(e.Attributes);
                this.OutputMemberScopeModifier(e.Attributes);
            }
        }
        else
        {
            this.OutputVTableModifier(e.Attributes);
        }
        this.OutputType(e.Type);
        this.Output.Write(" ");
        if ((e.PrivateImplementationType != null) && !this.IsCurrentInterface)
        {
            this.Output.Write(this.GetBaseTypeOutput(e.PrivateImplementationType));
            this.Output.Write(".");
        }
        if ((e.Parameters.Count > 0) && (string.Compare(e.Name, "Item", StringComparison.OrdinalIgnoreCase) == 0))
        {
            this.Output.Write("this[");
            this.OutputParameters(e.Parameters);
            this.Output.Write("]");
        }
        else
        {
            this.OutputIdentifier(e.Name);
        }
        this.OutputStartingBrace();
        this.Indent++;
        if (e.HasGet)
        {
            if (this.IsCurrentInterface || ((e.Attributes & MemberAttributes.ScopeMask) == MemberAttributes.Abstract))
            {
                this.Output.WriteLine("get;");
            }
            else
            {
                this.Output.Write("get");
                this.OutputStartingBrace();
                this.Indent++;
                this.GenerateStatements(e.GetStatements);
                this.Indent--;
                this.Output.WriteLine("}");
            }
        }
        if (e.HasSet)
        {
            if (this.IsCurrentInterface || ((e.Attributes & MemberAttributes.ScopeMask) == MemberAttributes.Abstract))
            {
                this.Output.WriteLine("set;");
            }
            else
            {
                this.Output.Write("set");
                this.OutputStartingBrace();
                this.Indent++;
                this.GenerateStatements(e.SetStatements);
                this.Indent--;
                this.Output.WriteLine("}");
            }
        }
        this.Indent--;
        this.Output.WriteLine("}");
    }
}

Carefully examining lines around if (e.HasGet), it seems impossible.


I don't think you can. The CodeDom namespace has been abandoned by Microsoft in favor of T4 code generation technology.

It has been a few years since anything new has been added there. I'm quite sure that the last addition was in .NET 2.0. And after that, not a thing.

So, if you're creating anything new that generates code, move to the T4.


Here is a LinqPad snippet that demonstrates how to do this. It is a major hack, since, as GregC's answer shows, it isn't possible using the structured Dom Classes. It basically generates the property, then edits the string to insert the attributes.

void Main()
{
    Sample sample = new Sample();
            sample.AddFields();
            sample.AddProperties();
            sample.AddMethod();
            sample.AddConstructor();
            sample.AddEntryPoint();
            sample.GenerateCSharpCode();
}

public class Sample{
        /// <summary> 
        /// Define the compile unit to use for code generation.  
        /// </summary>
        CodeCompileUnit targetUnit;

        /// <summary> 
        /// The only class in the compile unit. This class contains 2 fields, 
        /// 3 properties, a constructor, an entry point, and 1 simple method.  
        /// </summary>
        CodeTypeDeclaration targetClass;

        /// <summary> 
        /// The name of the file to contain the source code. 
        /// </summary> 
        private const string outputFileName = "SampleCode.cs";

        /// <summary> 
        /// Define the class. 
        /// </summary> 
        public Sample()
        {
            targetUnit = new CodeCompileUnit();
            CodeNamespace samples = new CodeNamespace("CodeDOMSample");
            samples.Imports.Add(new CodeNamespaceImport("System"));
            targetClass = new CodeTypeDeclaration("CodeDOMCreatedClass");
            targetClass.IsClass = true;
            targetClass.TypeAttributes =
                TypeAttributes.Public | TypeAttributes.Sealed;
            samples.Types.Add(targetClass);
            targetUnit.Namespaces.Add(samples);
        }

        /// <summary> 
        /// Adds two fields to the class. 
        /// </summary> 
        public void AddFields()
        {
            // Declare the widthValue field.
            CodeMemberField widthValueField = new CodeMemberField();
            widthValueField.Attributes = MemberAttributes.Private;
            widthValueField.Name = "widthValue";
            widthValueField.Type = new CodeTypeReference(typeof(System.Double));
            widthValueField.Comments.Add(new CodeCommentStatement(
                "The width of the object."));
            targetClass.Members.Add(widthValueField);

            // Declare the heightValue field
            CodeMemberField heightValueField = new CodeMemberField();
            heightValueField.Attributes = MemberAttributes.Private;
            heightValueField.Name = "heightValue";
            heightValueField.Type =
                new CodeTypeReference(typeof(System.Double));
            heightValueField.Comments.Add(new CodeCommentStatement(
                "The height of the object."));
            targetClass.Members.Add(heightValueField);
        }
        /// <summary> 
        /// Add three properties to the class. 
        /// </summary> 
        public void AddProperties()
        {
            // Declare the read-only Width property.
            CodeMemberProperty widthProperty = new CodeMemberProperty();
            widthProperty.Attributes =
                MemberAttributes.Public | MemberAttributes.Final;
            widthProperty.Name = "Width";
            widthProperty.HasGet = true;
            widthProperty.HasSet = true;
            widthProperty.Type = new CodeTypeReference(typeof(System.Double));
            widthProperty.Comments.Add(new CodeCommentStatement(
                "The Width property for the object."));
            widthProperty.GetStatements.Add(new CodeMethodReturnStatement(
                new CodeFieldReferenceExpression(
                new CodeThisReferenceExpression(), "widthValue")));
            widthProperty.SetStatements.Add(new CodeMethodReturnStatement(
                new CodeFieldReferenceExpression(
                new CodeThisReferenceExpression(), "widthValue")));
            targetClass.Members.Add(widthProperty);

            // Declare the read-only Height property.
            CodeMemberProperty heightProperty = new CodeMemberProperty();
            heightProperty.Attributes =
                MemberAttributes.Public | MemberAttributes.Final;
            heightProperty.Name = "Height";
            heightProperty.HasGet = true;
            heightProperty.Type = new CodeTypeReference(typeof(System.Double));
            heightProperty.Comments.Add(new CodeCommentStatement(
                "The Height property for the object."));
            heightProperty.GetStatements.Add(new CodeMethodReturnStatement(
                new CodeFieldReferenceExpression(
                new CodeThisReferenceExpression(), "heightValue")));
            targetClass.Members.Add(heightProperty);

            // Declare the read only Area property.
            CodeMemberProperty areaProperty = new CodeMemberProperty();
            areaProperty.Attributes =
                MemberAttributes.Public | MemberAttributes.Final;
            areaProperty.Name = "Area";
            areaProperty.HasGet = true;
            areaProperty.Type = new CodeTypeReference(typeof(System.Double));
            areaProperty.Comments.Add(new CodeCommentStatement(
                "The Area property for the object."));

            // Create an expression to calculate the area for the get accessor  
            // of the Area property.
            CodeBinaryOperatorExpression areaExpression =
                new CodeBinaryOperatorExpression(
                new CodeFieldReferenceExpression(
                new CodeThisReferenceExpression(), "widthValue"),
                CodeBinaryOperatorType.Multiply,
                new CodeFieldReferenceExpression(
                new CodeThisReferenceExpression(), "heightValue"));
            areaProperty.GetStatements.Add(
                new CodeMethodReturnStatement(areaExpression));
            targetClass.Members.Add(areaProperty);
        }

        /// <summary> 
        /// Adds a method to the class. This method multiplies values stored  
        /// in both fields. 
        /// </summary> 
        public void AddMethod()
        {
            // Declaring a ToString method
            CodeMemberMethod toStringMethod = new CodeMemberMethod();
            toStringMethod.Attributes =
                MemberAttributes.Public | MemberAttributes.Override;
            toStringMethod.Name = "ToString";
            toStringMethod.ReturnType =
                new CodeTypeReference(typeof(System.String));

            CodeFieldReferenceExpression widthReference =
                new CodeFieldReferenceExpression(
                new CodeThisReferenceExpression(), "Width");
            CodeFieldReferenceExpression heightReference =
                new CodeFieldReferenceExpression(
                new CodeThisReferenceExpression(), "Height");
            CodeFieldReferenceExpression areaReference =
                new CodeFieldReferenceExpression(
                new CodeThisReferenceExpression(), "Area");

            // Declaring a return statement for method ToString.
            CodeMethodReturnStatement returnStatement =
                new CodeMethodReturnStatement();

            // This statement returns a string representation of the width, 
            // height, and area. 
            string formattedOutput = "The object:" + Environment.NewLine +
                " width = {0}," + Environment.NewLine +
                " height = {1}," + Environment.NewLine +
                " area = {2}";
            returnStatement.Expression =
                new CodeMethodInvokeExpression(
                new CodeTypeReferenceExpression("System.String"), "Format",
                new CodePrimitiveExpression(formattedOutput),
                widthReference, heightReference, areaReference);
            toStringMethod.Statements.Add(returnStatement);
            targetClass.Members.Add(toStringMethod);
        }
        /// <summary> 
        /// Add a constructor to the class. 
        /// </summary> 
        public void AddConstructor()
        {
            // Declare the constructor
            CodeConstructor constructor = new CodeConstructor();
            constructor.Attributes =
                MemberAttributes.Public | MemberAttributes.Final;

            // Add parameters.
            constructor.Parameters.Add(new CodeParameterDeclarationExpression(
                typeof(System.Double), "width"));
            constructor.Parameters.Add(new CodeParameterDeclarationExpression(
                typeof(System.Double), "height"));

            // Add field initialization logic
            CodeFieldReferenceExpression widthReference =
                new CodeFieldReferenceExpression(
                new CodeThisReferenceExpression(), "widthValue");
            constructor.Statements.Add(new CodeAssignStatement(widthReference,
                new CodeArgumentReferenceExpression("width")));
            CodeFieldReferenceExpression heightReference =
                new CodeFieldReferenceExpression(
                new CodeThisReferenceExpression(), "heightValue");
            constructor.Statements.Add(new CodeAssignStatement(heightReference,
                new CodeArgumentReferenceExpression("height")));
            targetClass.Members.Add(constructor);
        }

        /// <summary> 
        /// Add an entry point to the class. 
        /// </summary> 
        public void AddEntryPoint()
        {
            CodeEntryPointMethod start = new CodeEntryPointMethod();
            CodeObjectCreateExpression objectCreate =
                new CodeObjectCreateExpression(
                new CodeTypeReference("CodeDOMCreatedClass"),
                new CodePrimitiveExpression(5.3),
                new CodePrimitiveExpression(6.9));

            // Add the statement: 
            // "CodeDOMCreatedClass testClass =  
            //     new CodeDOMCreatedClass(5.3, 6.9);"
            start.Statements.Add(new CodeVariableDeclarationStatement(
                new CodeTypeReference("CodeDOMCreatedClass"), "testClass",
                objectCreate));

            // Creat the expression: 
            // "testClass.ToString()"
            CodeMethodInvokeExpression toStringInvoke =
                new CodeMethodInvokeExpression(
                new CodeVariableReferenceExpression("testClass"), "ToString");

            // Add a System.Console.WriteLine statement with the previous  
            // expression as a parameter.
            start.Statements.Add(new CodeMethodInvokeExpression(
                new CodeTypeReferenceExpression("System.Console"),
                "WriteLine", toStringInvoke));
            targetClass.Members.Add(start);
        }
        /// <summary> 
        /// Generate CSharp source code from the compile unit. 
        /// </summary> 
        /// <param name="filename">Output file name</param>
        public void GenerateCSharpCode()
        {
            CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
            var options = new CodeGeneratorOptions(){
                BracingStyle = "C",
                IndentString = "\t", BlankLinesBetweenMembers = true};

            using (var sourceWriter = new StringWriter())
            {
            foreach(CodeNamespace @namespace in targetUnit.Namespaces){
                foreach(CodeTypeDeclaration type in @namespace.Types){
                    var items = new List<CodeTypeMember>();
                    foreach(CodeTypeMember codeMember in type.Members){
                        var property = codeMember as CodeMemberProperty;
                        if(property == null){ 
                            items.Add(codeMember);
                            continue;}
                        items.Add(new CodeSnippetTypeMember(GetPropertyTextWithGetSetLevelDebuggerNonUserCodeAttribute(provider, options, sourceWriter, property)));
                    }
                    type.Members.Clear();
                    type.Members.AddRange(items.ToArray());
                }
            }
            }

            using (StringWriter sourceWriter = new StringWriter())
            {
                provider.GenerateCodeFromCompileUnit(
                    targetUnit, sourceWriter, options);
                sourceWriter.ToString().Dump();
            }
        }

        private static string GetPropertyTextWithGetSetLevelDebuggerNonUserCodeAttribute(CodeDomProvider provider, CodeGeneratorOptions options, StringWriter sourceWriter, CodeMemberProperty property)
        {
            provider.GenerateCodeFromMember(property, sourceWriter, options);
            var code = sourceWriter.ToString();
            sourceWriter.GetStringBuilder().Clear();
            var lines = code.Split(new[] { Environment.NewLine }, StringSplitOptions.None).ToList();
            lines.RemoveAt(0);
            lines.RemoveAt(lines.Count -1);
            for (var i = lines.Count() - 1; i >= 0; i--)
            {
                var line = lines[i];
                lines[i] = "\t\t\t" + line;
                if (line.TrimStart() == "get" || line.TrimStart() == "set")
                {
                    //Insert attribute above
                    lines.Insert(i, "\t\t\t[System.Diagnostics.DebuggerNonUserCode()]");
                }
            }

            return String.Join(Environment.NewLine, lines.ToArray());
        }
// Define other methods and classes here
}

It generates this class:

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.34209
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace CodeDOMSample
{
  using System;


  public sealed class CodeDOMCreatedClass
  {

    // The width of the object.
    private double widthValue;

    // The height of the object.
    private double heightValue;

      // The Width property for the object.
      public double Width
      {
      [System.Diagnostics.DebuggerNonUserCode()]
        get
        {
          return this.widthValue;
        }
      [System.Diagnostics.DebuggerNonUserCode()]
        set
        {
          return this.widthValue;
        }
      }
      // The Height property for the object.
      public double Height
      {
      [System.Diagnostics.DebuggerNonUserCode()]
        get
        {
          return this.heightValue;
        }
      }
      // The Area property for the object.
      public double Area
      {
      [System.Diagnostics.DebuggerNonUserCode()]
        get
        {
          return (this.widthValue * this.heightValue);
        }
      }

    public CodeDOMCreatedClass(double width, double height)
    {
      this.widthValue = width;
      this.heightValue = height;
    }

    public override string ToString()
    {
      return string.Format("The object:\r\n width = {0},\r\n height = {1},\r\n area = {2}", this.Width, this.Height, this.Area);
    }

    public static void Main()
    {
      CodeDOMCreatedClass testClass = new CodeDOMCreatedClass(5.3D, 6.9D);
      System.Console.WriteLine(testClass.ToString());
    }
  }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜