我正在使用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]]
在我试图回答之前,我觉得有必要指出你正在做的事情似乎是多余的。假设您将此代码放入构造函数中,生成类似以下内容的内容:
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
。一旦建立起来,只需用new typename()
替换default(typename)
就可以满足您的需求。
更一般地说,您可以向该方法提供任何类型数组,以查看是否存在匹配的构造函数,或调用GetConstructors()
以获取所有类型。这里需要注意的是ConstructorInfo
的IsPublic
、IsStatic
和IsGenericMethod
字段,以找到一个你可以从生成此代码的地方实际调用
但是,您试图解决的问题将变得任意复杂,除非您可以对其进行一些约束。一种选择是找到一个任意构造函数并构建如下所示的调用:
var line = "this." + fieldName + " = new(";
foreach ( var param in constructor.GetParameters() )
{
line += "default(" + param.ParameterType.Name + "),";
}
line = line.TrimEnd(',') + ");"
(请注意,这只是为了说明目的,我可能会在这里使用 CodeDOM,或者至少使用 StringBuilder :)
但是,当然,现在您遇到了为每个参数确定适当的类型名称的问题,这些类型名称本身可能是泛型。引用类型参数将全部初始化为 null。而且无法知道您可以从任意多个构造函数中选择哪些构造函数实际上生成了可用对象(其中一些可能会做坏事,例如假设您将在构造实例后立即设置属性或调用方法。
如何解决这些问题不是技术问题:您可以递归地将相同的逻辑应用于每个参数,只要您愿意。对于您的用例,这是一个决定您需要有多复杂以及您愿意对用户施加什么样的限制的问题。
如果确定要使用字符串,则必须编写自己的方法来设置这些类型名称的格式。像这样:
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
。如果是值类型,则默认构造函数必须存在,因此您可以调用它。