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

发布于 2024-12-26 03:53:21 字数 1002 浏览 2 评论 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, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]

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]]

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

我早已燃尽 2025-01-02 03:53:21

在我尝试回答之前,我觉得有必要指出你所做的事情似乎是多余的。假设您将此代码放入构造函数中,则

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关键字的设计很大程度上是为了完成您正在做的事情:提供一种将“默认”值分配给编译时类型未知的变量的方法。我认为它是为了供通用代码使用而引入的,但自动生成的代码在使用它时当然也是正确的。

请记住,引用类型的默认值为null,因此

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

不会构造新的List,它只是设置<代码>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。并且无法知道您可以从任意多个构造函数中选择哪个实际生成可用的对象(其中一些可能会做坏事,例如假设您将在构造实例后立即设置属性或调用方法。)

如何解决这些问题不是技术问题:您可以将相同的逻辑递归地应用到每个参数,只要您愿意。这是一个根据您的用例决定需要多么复杂以及您愿意对用户设置什么样的限制的问题。

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.

吾性傲以野 2025-01-02 03:53:21

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

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。如果它是值类型,则默认构造函数必须存在,因此您可以调用它。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文