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());
}
}
}
精彩评论