0

我正在使用 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,版本=4.0.0.0,文化=中性,PublicKeyToken=b77a5c561934e089]]
4

2 回答 2

2

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

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.listnull。相反,我怀疑您想要做的是使用该Type.IsValueType属性将值类型保留为其默认值,并使用new.

最后,我认为您在这里寻找的是类的IsGenericType属性Type和相应的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,IsStatic和 ,IsGenericMethod的字段ConstructorInfo,以找到您可以从生成此代码的任何位置实际调用的字段。

但是,您尝试解决的问题将变得任意复杂,除非您可以对其施加一些限制。一种选择是找到一个任意构造函数并构建一个如下所示的调用:

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

(请注意,这仅用于说明目的,我可能会在这里使用 CodeDOM,或者至少使用 StringBuilder :)

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

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.

于 2012-01-10T18:43:57.910 回答
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的 s。

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

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

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

于 2012-01-10T18:43:27.707 回答