在运行时创建/修改枚举

我正在创建一个程序,用户可以选择创建自己的自定义属性,最终将显示在PropertyGrid 。 现在我不想搞乱自定义编辑器,所以我只允许PropertyGrid已经在编辑器中构建的原始类型属性( stringintdoubleDateTimebool等)。

但是,我还希望为用户提供创建多选属性的选项,他们可以在这些属性中定义可能值的列表,这些值又将显示为PropertyGrid的下拉列表。

当我在代码中对Enum进行硬编码时,属性网格会自动将该enum的属性显示为下拉列表。 但是我可以在运行时创建和/或修改枚举,以便用户可以添加另一个属性选项,并返回到PropertyGrid并在下拉列表中查看它们的新选项吗?

更新

考虑到Patricks的评论,我认为Enum在这种情况下不是正确的方法。 那么我怎样才能使用字符串列表填充PropertyGrid项目中的下拉列表? 这需要自定义编辑器吗?

答案是在一个简单的类中: TypeConverter 。 (是的,枚举不适合这里)。

由于我没有太多细节,我将假设您通过SelectedObject属性将一个PropertyGrid“链接”到目标实例,并且您的目标实例实现了ICustomTypeDescriptor,以便您可以在运行时添加属性(即PropertyDescriptors)。 我不知道你的设计,但如果你不这样做,我建议你去看看。

现在假设您添加了一个字符串属性,并且您希望让您的用户为此属性指定一组约束。 用户界面允许用户输入一组字符串,结果会得到一个字符串列表。 也许你在目标实例中保留了属性字典,所以让我们假设这个新列表也存储在那里。

现在,只需编写一个从TypeConverter派生的新转换器(或者在此示例中为StringConverter)。 您必须重写GetStandardValuesSupported以返回true,并使用GetStandardValues返回字符串列表(使用context参数访问Instance属性及其字符串列表)。 此转换器将由PropertyDescriptor使用PropertyDescriptor.Converter属性发布。

我希望这不是太模糊。 如果您对此过程有特定问题,请告诉我。

您的问题的典型工程解决方案是用于将列表维护为数据库中的参考数据。 通常,枚举是在编译时定义的常量,并且不鼓励在稍后发布的代码中修改它们(更不用说运行时),因为它可能会在switch语句中引起副作用。

您可以使用代码创建代码,然后将其保存到临时文本文件,然后使用它。 这涉及到使用HDD时会很慢。 我建议考虑反思 。

编辑:我在我的一本书中找到了一个完美的例子,这里是它(它很长但是如果你将它复制到VS中,它会更有意义)。

 namespace Programming_CSharp { using System; using System.Diagnostics; using System.IO; using System.Reflection; using System.Reflection.Emit; using System.Threading; // used to benchmark the looping approach public class MyMath { // sum numbers with a loop public int DoSumLooping(int initialVal) { int result = 0; for(int i = 1;i <=initialVal;i++) { result += i; } return result; } } // declare the interface public interface IComputer { int ComputeSum( ); } public class ReflectionTest { // the private method which emits the assembly // using op codes private Assembly EmitAssembly(int theValue) { // Create an assembly name AssemblyName assemblyName = new AssemblyName( ); assemblyName.Name = "DoSumAssembly"; // Create a new assembly with one module AssemblyBuilder newAssembly = Thread.GetDomain( ).DefineDynamicAssembly( assemblyName, AssemblyBuilderAccess.Run); ModuleBuilder newModule = newAssembly.DefineDynamicModule("Sum"); // Define a public class named "BruteForceSums " // in the assembly. TypeBuilder myType = newModule.DefineType( "BruteForceSums", TypeAttributes.Public); // Mark the class as implementing IComputer. myType.AddInterfaceImplementation( typeof(IComputer)); // Define a method on the type to call. Pass an // array that defines the types of the parameters, // the type of the return type, the name of the // method, and the method attributes. Type[] paramTypes = new Type[0]; Type returnType = typeof(int); MethodBuilder simpleMethod = myType.DefineMethod( "ComputeSum", MethodAttributes.Public | MethodAttributes.Virtual, returnType, paramTypes); // Get an ILGenerator. This is used // to emit the IL that you want. ILGenerator generator = simpleMethod.GetILGenerator( ); // Emit the IL that you'd get if you // compiled the code example // and then ran ILDasm on the output. // Push zero onto the stack. For each 'i' // less than 'theValue', // push 'i' onto the stack as a constant // add the two values at the top of the stack. // The sum is left on the stack. generator.Emit(OpCodes.Ldc_I4, 0); for (int i = 1; i <= theValue;i++) { generator.Emit(OpCodes.Ldc_I4, i); generator.Emit(OpCodes.Add); } // return the value generator.Emit(OpCodes.Ret); //Encapsulate information about the method and //provide access to the method's metadata MethodInfo computeSumInfo = typeof(IComputer).GetMethod("ComputeSum"); // specify the method implementation. // Pass in the MethodBuilder that was returned // by calling DefineMethod and the methodInfo // just created myType.DefineMethodOverride(simpleMethod, computeSumInfo); // Create the type. myType.CreateType( ); return newAssembly; } // check if the interface is null // if so, call Setup. public double DoSum(int theValue) { if (theComputer == null) { GenerateCode(theValue); } // call the method through the interface return (theComputer.ComputeSum( )); } // emit the assembly, create an instance // and get the interface public void GenerateCode(int theValue) { Assembly theAssembly = EmitAssembly(theValue); theComputer = (IComputer) theAssembly.CreateInstance("BruteForceSums"); } // private member data IComputer theComputer = null; } public class TestDriver { public static void Main( ) { const int val = 2000; // Note 2,000 // 1 million iterations! const int iterations = 1000000; double result = 0; // run the benchmark MyMath m = new MyMath( ); DateTime startTime = DateTime.Now; for (int i = 0;i < iterations;i++) result = m.DoSumLooping(val); } TimeSpan elapsed = DateTime.Now - startTime; Console.WriteLine( "Sum of ({0}) = {1}",val, result); Console.WriteLine( "Looping. Elapsed milliseconds: " + elapsed.TotalMilliseconds + " for {0} iterations", iterations); // run our reflection alternative ReflectionTest t = new ReflectionTest( ); startTime = DateTime.Now; for (int i = 0;i < iterations;i++) { result = t.DoSum(val); } elapsed = DateTime.Now - startTime; Console.WriteLine( "Sum of ({0}) = {1}",val, result); Console.WriteLine( "Brute Force. Elapsed milliseconds: " + elapsed.TotalMilliseconds + " for {0} iterations", iterations); } } } 

输出:(2000)= 2001000之和
循环。 经过的毫秒数:
11468.75对于1000000次迭代
和(2000)= 2001000
蛮力。 经过的毫秒数:
406.25用于1000000次迭代

如果您想了解更多信息,请参阅整章的链接。

您可以使用Enum.GetNames()和Enum.GetValues()来检索值并动态地向它们添加新值。 虽然我建议你使用列表而不是枚举或重新考虑你的设计。 东西闻起来不对劲。