代码生成中的属性/字段初始值设定项

时间:2012-01-10 18:05:29

标签: c# generics reflection properties initializer

我正在使用CodeDom和普通代码字符串在visual studio扩展中生成代码。我的扩展使用反射读取当前类声明的字段和属性,并生成构造函数,初始化器,实现某些接口等。

生成器类很简单:

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 + ")";  
    }  
}

我以两种方式坚持使用字段和属性初始化程序。

首先,虽然default(AnyNonGenericValueOrReferenceType)似乎在大多数情况下都有效,但我对在生成的代码中使用它感到不舒服。

其次,它不适用于泛型类型,因为我找不到获取泛型类型的基础类型的方法。因此,如果属性为List < int >,则property.PropertyType.Name会返回List`1。这里有两个问题。首先,我需要在不使用字符串操作的情况下获取泛型类型的正确名称。其次,我需要访问底层类型。完整的属性类型名称返回如下内容:

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

2 个答案:

答案 0 :(得分:1)

如果您确定要使用字符串,则必须编写自己的方法来格式化这些类型名称。类似的东西:

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;
}

此代码假定您的文件中包含所有必需的using

但我认为实际使用CodeDOM的对象模型要好得多。这样,您就不必担心using,格式化类型或拼写错误:

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

如果你真的不想使用default(T),你可以找出该类型是引用类型还是值类型。如果是引用类型,请使用null。如果是值类型,则默认构造函数必须存在,因此您可以调用它。

答案 1 :(得分:1)

在我尝试回答之前,我不得不指出你正在做的事似乎是多余的。假设您将此代码放入构造函数中,生成类似于:

的内容
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);
  }
}

是不必要的。在构造类时,已经自动发生。 (事实上​​,一些快速测试表明,如果在构造函数中明确地完成这些赋值,那么这些赋值甚至都不会被优化掉,尽管我认为JITter可以处理它。)

其次,default关键字的设计在很大程度上是为了完成您正在做的事情:提供一种方法将“默认”值分配给在编​​译时类型未知的变量。我假设它是由通用代码引入使用的,但自动生成的代码在使用它时肯定是正确的。

请注意,引用类型的default值为null,因此

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

不会构建新的List<int>,只会将this.list设置为null。我怀疑你想要做的是使用Type.IsValueType属性将值类型保留为默认值,并使用new初始化引用类型。

最后,我认为您在这里寻找的是Type类的IsGenericType属性以及相应的GetGenericArguments()方法:

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 + ")";  
  }
}

编辑:

就构造对引用类型有用的东西而言,我所看到的生成代码使用的常见技术是要求您使用的任何类的无参数构造函数。通过调用Type.GetConstructor(),传入一个空Type[](例如Type.EmptyTypes)并查看它是否返回ConstructorInfo,可以很容易地查看某个类是否具有无参数构造函数或null。一旦确定,只需将default(typename)替换为new typename()即可达到您的需要。

更一般地说,您可以向该方法提供任何类型的数组,以查看是否存在匹配的构造函数,或者调用GetConstructors()来获取所有类型。需要注意的是IsPublic的{​​{1}},IsStaticIsGenericMethod字段,以便在生成此代码的任何位置找到您实际可以调用的字段。

然而,你试图解决的问题将变得任意复杂,除非你可以对它施加一些约束。一种选择是找到一个任意构造函数并构建一个如下所示的调用:

ConstructorInfo

(注意这只是为了说明的目的,我可能在这里使用CodeDOM,或者至少使用StringBuilder:)

但是,当然,现在您遇到了为每个参数确定适当类型名称的问题,这些参数本身可能是泛型。并且引用类型参数都将初始化为null。并且无法知道您可以从中选择哪个任意多个构造函数实际生成一个可用对象(其中一些可能会做坏事,例如假设您将在构造实例后立即设置属性或调用方法。)

如何解决这些问题并不是一个技术问题:您可以递归地将同样的逻辑应用于您愿意去的每个参数。根据您的使用案例,您需要确定您需要的复杂程度以及您愿意对用户施加何种限制。