注意:我使用的是.Net 1.1,虽然我并不完全反对使用更高版本的答案。
我在PropertyGrid中显示一些动态生成的对象。这些对象具有数字,文本和枚举属性。目前,我在设置枚举的默认值时遇到问题,因此它们并不总是在列表中显示为粗体。枚举本身也是动态生成的,除了默认值外似乎工作正常。
首先,我想说明如何在导致错误的情况下生成枚举。第一行使用自定义类来查询数据库。只需使用DataAdapter或使用数据库值填充DataSet的首选方法替换此行。我使用第1列中的字符串值来创建枚举。
private Type GetNewObjectType(string field, ModuleBuilder module, DatabaseAccess da)
//Query the database.
System.Data.DataSet ds = da.QueryDB(query);
EnumBuilder eb = module.DefineEnum(field, TypeAttributes.Public, typeof(int));
for(int i = 0; i < ds.Tables[0].Rows.Count; i++)
{
if(ds.Tables[0].Rows[i][1] != DBNull.Value)
{
string text = Convert.ToString(ds.Tables[0].Rows[i][1]);
eb.DefineLiteral(text, i);
}
}
return eb.CreateType();
现在介绍如何创建类型。这主要基于提供的示例代码here。实质上,将pFeature视为数据库行。我们遍历列并使用列名作为新属性名称,并使用列值作为默认值;至少这是目标。
// create a dynamic assembly and module
AssemblyName assemblyName = new AssemblyName();
assemblyName.Name = "tmpAssembly";
AssemblyBuilder assemblyBuilder = System.Threading.Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
ModuleBuilder module = assemblyBuilder.DefineDynamicModule("tmpModule");
// create a new type builder
TypeBuilder typeBuilder = module.DefineType("BindableRowCellCollection", TypeAttributes.Public | TypeAttributes.Class);
// Loop over the attributes that will be used as the properties names in out new type
for(int i = 0; i < pFeature.Fields.FieldCount; i++)
{
string propertyName = pFeature.Fields.get_Field(i).Name;
object val = pFeature.get_Value(i);
Type type = GetNewObjectType(propertyName, module, da);
// Generate a private field
FieldBuilder field = typeBuilder.DefineField("_" + propertyName, type, FieldAttributes.Private);
// Generate a public property
PropertyBuilder property =
typeBuilder.DefineProperty(propertyName,
PropertyAttributes.None,
type,
new Type[0]);
//Create the custom attribute to set the description.
Type[] ctorParams = new Type[] { typeof(string) };
ConstructorInfo classCtorInfo =
typeof(DescriptionAttribute).GetConstructor(ctorParams);
CustomAttributeBuilder myCABuilder = new CustomAttributeBuilder(
classCtorInfo,
new object[] { "This is the long description of this property." });
property.SetCustomAttribute(myCABuilder);
//Set the default value.
ctorParams = new Type[] { type };
classCtorInfo = typeof(DefaultValueAttribute).GetConstructor(ctorParams);
if(type.IsEnum)
{
//val contains the text version of the enum. Parse it to the enumeration value.
object o = Enum.Parse(type, val.ToString(), true);
myCABuilder = new CustomAttributeBuilder(
classCtorInfo,
new object[] { o });
}
else
{
myCABuilder = new CustomAttributeBuilder(
classCtorInfo,
new object[] { val });
}
property.SetCustomAttribute(myCABuilder);
// The property set and property get methods require a special set of attributes:
MethodAttributes GetSetAttr =
MethodAttributes.Public |
MethodAttributes.HideBySig;
// Define the "get" accessor method for current private field.
MethodBuilder currGetPropMthdBldr =
typeBuilder.DefineMethod("get_value",
GetSetAttr,
type,
Type.EmptyTypes);
// Intermediate Language stuff...
ILGenerator currGetIL = currGetPropMthdBldr.GetILGenerator();
currGetIL.Emit(OpCodes.Ldarg_0);
currGetIL.Emit(OpCodes.Ldfld, field);
currGetIL.Emit(OpCodes.Ret);
// Define the "set" accessor method for current private field.
MethodBuilder currSetPropMthdBldr =
typeBuilder.DefineMethod("set_value",
GetSetAttr,
null,
new Type[] { type });
// Again some Intermediate Language stuff...
ILGenerator currSetIL = currSetPropMthdBldr.GetILGenerator();
currSetIL.Emit(OpCodes.Ldarg_0);
currSetIL.Emit(OpCodes.Ldarg_1);
currSetIL.Emit(OpCodes.Stfld, field);
currSetIL.Emit(OpCodes.Ret);
// Last, we must map the two methods created above to our PropertyBuilder to
// their corresponding behaviors, "get" and "set" respectively.
property.SetGetMethod(currGetPropMthdBldr);
property.SetSetMethod(currSetPropMthdBldr);
}
// Generate our type
Type generatedType = typeBuilder.CreateType();
最后,我们使用该类型创建它的实例并加载默认值,以便稍后使用PropertiesGrid显示它。
// Now we have our type. Let's create an instance from it:
object generatedObject = Activator.CreateInstance(generatedType);
// Loop over all the generated properties, and assign the default values
PropertyInfo[] properties = generatedType.GetProperties();
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(generatedType);
for(int i = 0; i < properties.Length; i++)
{
string field = properties[i].Name;
DefaultValueAttribute dva = (DefaultValueAttribute)props[field].Attributes[typeof(DefaultValueAttribute)];
object o = dva.Value;
Type pType = properties[i].PropertyType;
if(pType.IsEnum)
{
o = Enum.Parse(pType, o.ToString(), true);
}
else
{
o = Convert.ChangeType(o, pType);
}
properties[i].SetValue(generatedObject, o, null);
}
return generatedObject;
但是,当我们尝试获取枚举的默认值时,这会导致错误。 DefaultValueAttribute dva没有设置,因此当我们尝试使用它时会导致异常。
如果我们更改此代码段:
if(type.IsEnum)
{
object o = Enum.Parse(type, val.ToString(), true);
myCABuilder = new CustomAttributeBuilder(
classCtorInfo,
new object[] { o });
}
到此:
if(type.IsEnum)
{
myCABuilder = new CustomAttributeBuilder(
classCtorInfo,
new object[] { 0 });
}
获取DefaultValueAttribute dva没有问题;但是,该字段然后在PropertiesGrid中加粗,因为它与默认值不匹配。
当我将默认值设置为生成的枚举时,有谁可以找出为什么我无法获取DefaultValueAttribute?正如你可能猜到的那样,我仍然是Reflection的新手,所以这对我来说都很新鲜。
感谢。
更新:响应alabamasucks.blogspot,使用ShouldSerialize肯定会解决我的问题。我能够使用普通类创建方法;但是,我不确定如何为生成的类型执行此操作。根据我的判断,我需要使用MethodBuilder并生成IL以检查该字段是否等于默认值。听起来很简单。我想用IL代码表示这个:
public bool ShouldSerializepropertyName()
{
return (field != val);
}
我能够使用类似代码中的ildasm.exe获取IL代码,但我有几个问题。如何在IL代码中使用val变量?在我的例子中,我使用了一个值为0的int。
IL_0000: ldc.i4.s 0
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: ldarg.0
IL_0005: ldfld int32 TestNamespace.TestClass::field
IL_000a: ceq
IL_000c: ldc.i4.0
IL_000d: ceq
IL_000f: stloc.1
IL_0010: br.s IL_0012
IL_0012: ldloc.1
IL_0013: ret
这肯定会变得棘手,因为IL对每种类型都有不同的加载命令。目前,我使用整数,双精度,字符串和枚举,因此代码必须根据类型进行自适应。
有谁知道如何做到这一点?还是我朝错误的方向前进?
答案 0 :(得分:3)
我不确定如何使该属性起作用,但还有另一种选择可能更容易。
除了检查DefaultValueAttribute之外,PropertyGrid还使用反射来查找名为“ShouldSerializeProperty Name”的方法,其中[Property Name]是相关属性的名称。此方法应返回一个布尔值,如果该属性设置为非默认值,则返回true,否则返回false。您可能更容易使用反射来创建一个方法,该方法返回正确的值然后修复属性。
答案 1 :(得分:2)
您应该使用DefaultValueAttribute尝试使用String和Type参数,传入字符串枚举值(val.ToString)以及枚举的类型。