【问题标题】:Property / field initializers in code generation代码生成中的属性/字段初始值设定项
【发布时间】:2012-02-07 03:40:49
【问题描述】:

我正在使用 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 &lt; int &gt;property.PropertyType.Name 返回List`1。这里有两个问题。首先,我需要在不使用字符串操作的情况下获取泛型类型的正确名称。其次,我需要访问底层类型。完整的属性类型名称返回如下内容:

System.Collections.Generic.List`1[[System.Int32,mscorlib,版本=4.0.0.0,文化=中性,PublicKeyToken=b77a5c561934e089]]

【问题讨论】:

  • 您能否详细说明为什么不喜欢使用default(T)
  • 这不是一个炫技,但我宁愿使用 CodeDom 来生成一个普通用户熟悉的初始化程序。仍然有很多开发人员没有遇到过深入的泛型,你会偶然发现 default(T) 构造。
  • 你为什么还要生成这段代码?在构造函数运行之前,所有字段都设置为默认值。这包括自动属性的支持字段。
  • 请看我对下面答案的评论。
  • 我正在尝试这两种方法,并将相应地标记答案。谢谢两位。顺便说一句,我们可以将多个答案标记为正确吗?

标签: c# generics reflection properties initializer


【解决方案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&lt;int&gt;,它只是将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),可以很容易地查看一个类是否有一个无参数的构造函数,然后查看它是否返回ConstructorInfonull。一旦确定,只需将default(typename) 替换为new typename() 即可满足您的需求。

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

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

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

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

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

解决这些问题的方式与技术无关:您可以递归地将相同的逻辑应用于每个参数,只要您愿意。这是一个决定,对于您的用例,您需要有多复杂,以及您愿意对用户施加什么样的限制。

【讨论】:

  • 你在说什么代码分析?
  • 旁注:有人知道如何在 SEML 中转义反引号吗?
  • 嗯。好吧,我说的是 Visual Studio 的 CA1805,“不要进行不必要的初始化”;但似乎他们已经从 VS2010 中删除了该特定规则。不知道为什么,它仍然是不必要的代码。 耸耸肩
  • @MichaelEdenfield,你肯定明白我在这里想要做什么。关于冗余,这种情况需要构象而不是优化。我可以看到您作为用户如何期望扩展生成最优化的代码。然而,就我而言,我使用扩展来确保遵守约定。在开发的后期阶段,我们使用自己的代码分析器来确保优化。
  • @RaheelKhan,我认为不编写无用的代码对读者的优化比性能更重要。
【解决方案2】:

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

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

此代码假定您的文件中有所有必要的usings。

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

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

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

【讨论】:

  • 谢谢。我将试一试您的 CodeDom,看看它与通用引用类型的关系如何。我有一种感觉,我会被嵌套泛型卡住。
  • 到目前为止,第一种方法效果很好。我添加了完整的命名空间并得到:System.Collections.Generic.List &lt; System.Collections.Generic.List &lt; Entity &gt; &gt;。我怎样才能完全符合Entity 的资格?现在将尝试 CodeDom 以查看它是否会处理生成对复杂构造函数的正确调用。
  • 顺便说一句,如果我没有澄清,我不想将引用类型设置为 null。当属性用某些自定义属性(特别是集合)标记时,它们只需要在构造函数中创建一次。反射方法非常适合插入正确的类型声明。
  • 啊!为什么我没有猜到 CodeDom 会做同样的事情。有没有办法以编程方式确定默认构造函数(如果没有明确定义)并生成代码来调用它?或为此明确定义的内容之一。假设反射和代码域在编译时具有双向关系,我错了吗?毕竟反汇编程序不是这样做的吗?
  • @RaheelKhan,我不确定你所说的双向关系是什么意思。但是你当然可以使用反射来判断一个类型是否有无参数构造函数,然后使用 CodeDOM 生成调用它的代码。
猜你喜欢
  • 2018-02-12
  • 2011-11-16
  • 2013-01-04
  • 2022-01-23
相关资源
最近更新 更多