Property / field initializers in code generation

喜夏-厌秋 提交于 2019-12-25 02:16:14

问题


I'm generating code in a visual studio extension using CodeDom and plain code strings. My extension reads a current classes declared fields and properties using reflection and generates contructors, initializers, implements certain interfaces, etc.

The generator class is simple:

public class CodeGenerator < T >  
{  
    public string GetCode ()  
    {  
        string code = "";  
        T type = typeof(T);  
        List < PropertyInfo > properties = t.GetProperties();  
        foreach (PropertyInfo property in properties)  
            code += "this." + property.Name + " = default(" + property.PropertyType.Name + ")";  
    }  
}

I'm stuck at field and property initializers in two ways.

Firstly, although default(AnyNonGenericValueOrReferenceType) seems to work in most cases, I'm uncomfortable with using it in generated code.

Secondly, it does not work for generic types since I can't find a way to get the underlying type of the generic type. So if a property is List < int >, property.PropertyType.Name returns List`1. There are two problems here. First, I need to get the proper name for the generic type without using string manipulation. Second, I need to access the underlying type. The full property type name returns something like:

System.Collections.Generic.List`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]

回答1:


If you are sure you want to use strings, you will have to write your own method to format those type names. Something like:

static string FormatType(Type t)
{
    string result = t.Name;

    if (t.IsGenericType)
    {
        result = string.Format("{0}<{1}>",
            result.Split('`')[0],
            string.Join(",", t.GetGenericArguments().Select(FormatType)));
    }

    return result;
}

This code assumes you have all necessary usings in your file.

But I think it's much better to actually use CodeDOM's object model. This way, you don't have to worry about usings, formatting types or typos:

var statement =
    new CodeAssignStatement(
        new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), property.Name),
        new CodeDefaultValueExpression(new CodeTypeReference(property.PropertyType)));

And if you really don't want to use default(T), you can find out whether the type is a reference or value type. If it's a reference type, use null. If it's value type, the default constructor has to exist, and so you can call that.




回答2:


Before I try to answer, I feel compelled to point out that what you're doing seems redundant. Assuming that you are putting this code into a constructor, generating something like:

public class Foo
{
  private int a;
  private bool b;
  private SomeType c;

  public Foo()
  {
    this.a = default(int);
    this.b = default(bool);
    this.c = default(SomeType);
  }
}

is unnecessary. That already happens automatically when a class is constructed. (In fact, some quick testing shows that these assignments aren't even optimized away if they're done explicitly in the constructor, though I suppose the JITter could take care of that.)

Second, the default keyword was designed in large part to do exactly what you're doing: to provide a way to assign the "default" value to a variable whose type is unknown at compile time. It was introduced for use by generic code, I assume, but auto-generated code is certainly correct in using it as well.

Keep in mind that the default value of a reference type is null, so

this.list = default(List<int>);

does not construct a new List<int>, it just sets this.list to null. What I suspect you want to do, instead, is to use the Type.IsValueType property to leave value types at their default values, and initialize reference types using new.

Lastly, I think what you're looking for here is the IsGenericType property of the Type class and the corresponding GetGenericArguments() method:

foreach (PropertyInfo property in properties)  
{
  if (property.Type.IsGenericType)
  {
    var subtypes = property.Type.GetGenericArguments();
    // construct full type name from type and subtypes.
  }
  else
  {
    code += "this." + property.Name + " = default(" + property.PropertyType.Name + ")";  
  }
}

EDIT:

As far as constructing something useful for a reference type, a common technique I've seen used by generated code is to require a parameterless constructor for any class that you expect to use. It's easy enough to see if a class has a parameterless constructor, by calling Type.GetConstructor(), passing in an empty Type[] (e.g. Type.EmptyTypes), and see if it returns a ConstructorInfo or null. Once that has been established, simply replacing default(typename) with new typename() should achieve what you need.

More generally you can supply any array of types to that method to see if there's a matching constructor, or call GetConstructors() to get them all. Things to look out for here are the IsPublic, IsStatic, and IsGenericMethod fields of the ConstructorInfo, to find one you can actually call from wherever this code is being generated.

The problem you are trying to solve, though, is going to become arbitrarily complex unless you can place some constraints on it. One option would be to find an arbitrary constructor and build a call that looks like this:

var line = "this." + fieldName + " = new(";
foreach ( var param in constructor.GetParameters() )
{
  line += "default(" + param.ParameterType.Name + "),";
}
line = line.TrimEnd(',') + ");"

(Note this is for illustrative purposes only, I'd probably use CodeDOM here, or at least a StringBuilder :)

But of course, now you have the problem of determining the appropriate type name for each parameter, which themselves could be generics. And the reference type parameters would all be initialized to null. And there's no way of knowing which of the arbitrarily many constructors you can pick from actually produces a usable object (some of them may do bad things, like assume you're going to set properties or call methods immediately after you construct an instance.)

How you go about solving those issues is not a technical one: you can recursively apply this same logic to each parameter as far down as you're willing to go. It's a matter of deciding, for your use case, how complex you need to be and what kind of limits you're willing to place on the users.



来源:https://stackoverflow.com/questions/8808165/property-field-initializers-in-code-generation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!