Примечание. Я использую .Net 1.1, хотя я не полностью против ответа, использующего более высокие версии.PropertyGrid, DefaultValueAttribute, динамический объект и перечисления
Я показываю некоторые динамически сгенерированные объекты в 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; однако поле затем выделено жирным шрифтом в PropertyGrid, потому что оно не соответствует значению по умолчанию.
Может ли кто-нибудь понять, почему я не могу получить атрибут DefaultValueAttribute, когда я установил значение по умолчанию для моего сгенерированного перечисления? Как вы, наверное, догадались, я до сих пор новичок в Reflection, поэтому для меня это совсем не ново.
Спасибо.
Обновление: В ответ на alabamasucks.blogspot использование функции ShouldSerialize, безусловно, решит мою проблему. Я смог создать метод, используя обычный класс; однако я не уверен, как это сделать для сгенерированного типа. Из того, что я могу понять, мне нужно будет использовать MethodBuilder и сгенерировать IL, чтобы проверить, совпадает ли поле с значением по умолчанию. Звучит достаточно просто. Я хочу, чтобы представить это в IL код:
public bool ShouldSerializepropertyName()
{
return (field != val);
}
Я был в состоянии получить код IL, используя ildasm.exe из кода, но у меня есть несколько вопросов. Как использовать переменную val в коде IL? В моем примере я использовал Int со значением 0.
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 имеет другую команду загрузки для каждого типа.В настоящее время я использую ints, doubles, strings и перечисления, поэтому код должен быть адаптивным в зависимости от типа.
Есть ли у кого-нибудь идеи, как это сделать? Или я направляюсь в неправильном направлении?
ShouldSerialize [Property Name] является интересным лакомым кусочком. Это в вики-страницах «скрытых функций»? –
Он задокументирован в MSDN по адресу http://msdn.microsoft.com/en-us/library/53b8022e (VS.71,classic).aspx. Очевидно, что это не позволяет легко обнаружить самостоятельно, если вы не пытаетесь найти подходящую вещь. –