在运行时创建/修改枚举类型

9
我正在创建一个程序,用户可以选择创建自己的自定义属性,最终将在PropertyGrid中显示。现在我不想使用自定义编辑器,所以我只允许使用基本类型属性(stringintdoubleDateTimebool等),这些类型的PropertyGrid已经有内置的编辑器。
然而,我还想给用户提供创建多选属性的选项,他们可以定义可能的值列表,这些值将显示为PropertyGrid中的下拉列表。
当我在代码中硬编码一个Enum时,属性网格会自动将该enum的属性显示为下拉列表。但是我能否在运行时创建和修改枚举,以便用户可以添加另一个属性选项,并返回到PropertyGrid中,在下拉列表中看到他们的新选项?
更新:
考虑到Patrick的评论,我认为在这种情况下Enum不是正确的方法。那么我该如何使用字符串列表来填充PropertyGrid项目中的下拉列表?是否需要自定义编辑器?
4个回答

5
答案在一个简单的类中:TypeConverter。(是的,枚举类型不适用于此处。)
由于我没有很多细节,我假设您有一个PropertyGrid“链接”到通过SelectedObject属性与目标实例相关联,并且您的目标实例实现了ICustomTypeDescriptor,以便您可以在运行时添加属性(即PropertyDescriptor)。我不知道您的设计,但如果您不这样做,我建议您看一下它。
现在假设您添加了一个字符串属性,并且您想让用户为此属性指定一组约束条件。您的UI允许用户输入一组字符串,并将其作为结果获得字符串列表。也许您在目标实例中保留了属性字典,因此让我们假设这个新列表也存储在那里。
现在,只需编写一个派生自TypeConverter(或在此示例中可能是StringConverter)的新转换器。您将需要覆盖GetStandardValuesSupported以返回true,并使用context参数访问Instance属性及其字符串列表来返回字符串列表。此转换器将由您的PropertyDescriptor与PropertyDescriptor.Converter属性一起发布。
希望这不会太模糊。如果您对此过程有具体问题,请告诉我。

3

针对您的问题,典型的工程解决方案是在数据库中维护列表作为参考数据。通常情况下,枚举是在编译时定义为常量,并且不建议在以后的代码版本中进行修改(更不用说运行时),因为这可能会导致 switch 语句中出现副作用。


0
你可以使用你的代码创建代码,然后将其保存到临时文本文件中,然后再使用它。这样做会很慢,因为它涉及使用硬盘驱动器。我建议研究一下reflection编辑:我在我的书中找到了一个完美的例子,在这里(它相当冗长,但如果你将它复制到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
循环。已过去的毫秒数:
1000000次迭代用时11468.75毫秒
2000的和=2001000
暴力枚举法。已过去的毫秒数:
1000000次迭代用时406.25毫秒

这里是整个章节的链接,如果您想了解更多信息。


1
我真的很讨厌人们在没有解释原因的情况下对答案进行负评。这个答案不起作用吗?它没有回答问题吗? - Tarynn

-7
你可以使用 Enum.GetNames() 和 Enum.GetValues() 来检索值并动态地添加新值。虽然我建议你使用列表而不是枚举,或者重新考虑你的设计。有些地方似乎不太对劲。

我们能通过反射来添加它们吗? - Srikar Doddi
GETNames和GETValues应该会让你明白,你不能通过这些方法来改变枚举。 - Ole Albers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接