Создание/изменение перечислений во время выполнения

Я создаю программу, в которой у пользователя есть возможность создавать свои собственные настраиваемые свойства, которые в конечном итоге будут отображаться в файле PropertyGrid. Прямо сейчас я не хочу связываться с пользовательскими редакторами, поэтому я разрешаю только свойства примитивного типа (string, int, double, DateTime, bool и т. д.), для которых PropertyGrid уже имеет встроенные редакторы.

Однако я также хочу дать пользователю возможность создавать свойства с множественным выбором, где они могут определить список возможных значений, которые, в свою очередь, будут отображаться в виде раскрывающегося списка в PropertyGrid.

Когда я жестко кодирую Enum в своем коде, сетка свойств автоматически отображает свойства этого enum в виде раскрывающегося списка. Но могу ли я создать и/или изменить перечисление во время выполнения, чтобы пользователь мог добавить другой параметр свойства, вернуться к PropertyGrid и увидеть свой новый параметр в раскрывающемся списке?

Обновить

Учитывая комментарий Патрика, я думаю, что Enum в этом случае - неправильный путь. Итак, как я могу использовать список строк для заполнения раскрывающегося списка в элементе PropertyGrid? Потребуется ли для этого специальный редактор?


person Eric Anastas    schedule 14.04.2009    source источник


Ответы (4)


Ответ находится в простом классе: TypeConverter. (и да, перечисления здесь не подходят).

Поскольку у меня мало подробностей, я предполагаю, что у вас есть PropertyGrid, "связанный" с целевым экземпляром с помощью свойства SelectedObject, и что ваш целевой экземпляр реализует ICustomTypeDescriptor, чтобы вы могли добавлять свойства (т.е. PropertyDescriptors) во время выполнения. Я не знаю вашего дизайна, но если вы не делаете этого, я советую вам взглянуть на него.

Теперь предположим, что вы добавили строковое свойство и хотите, чтобы ваш пользователь задавал набор ограничений для этого свойства. Ваш пользовательский интерфейс позволяет пользователю вводить набор строк, и в результате вы получаете список строк. Возможно, вы храните словарь свойств в целевом экземпляре, поэтому давайте предположим, что этот новый список тоже хранится там.

Теперь просто напишите новый преобразователь, производный от TypeConverter (или, возможно, StringConverter в этом примере). Вам придется переопределить GetStandardValuesSupported, чтобы вернуть true, и GetStandardValues, чтобы вернуть список строк (используйте параметр контекста для доступа к свойству Instance и его списку строк). Этот конвертер будет опубликован вашим PropertyDescriptor со свойством PropertyDescriptor.Converter.

Я надеюсь, что это не слишком туманно. Если у вас есть конкретный вопрос по этому процессу, просто дайте мне знать.

person Nicolas Cadilhac    schedule 04.09.2009

Типичным инженерным решением вашей проблемы является использование списка в качестве справочных данных в вашей базе данных. Как правило, перечисления должны быть константами, определенными во время компиляции, и их модификация в более позднем выпуске кода не рекомендуется (не говоря уже о времени выполнения), поскольку это может вызвать побочные эффекты в операторах switch.

person Community    schedule 14.04.2009

Вы можете создать код, используя свой код, затем сохранить его во временный текстовый файл, а затем использовать его. Это было бы медленно, так как требует использования жесткого диска. Я бы рекомендовал изучить отражение.

Редактировать: я нашел идеальный пример в одной из своих книг, вот он (он довольно длинный, но если вы скопируете его в 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 на 1 000 000 итераций

Вот ссылка на всю главу, если вам нужна дополнительная информация.

person Kredns    schedule 14.04.2009
comment
Я действительно ненавижу, когда люди голосуют против ответа, не объясняя, почему. Это не работает? Разве это не ответ на вопрос? - person Tarynn; 17.12.2012

Вы можете использовать Enum.GetNames() и Enum.GetValues() для извлечения значений и динамического добавления к ним новых. хотя я предлагаю вам использовать список вместо перечисления или переосмыслить свой дизайн. что-то не так пахнет.

person Srikar Doddi    schedule 14.04.2009
comment
мы можем добавить их через отражение? - person Srikar Doddi; 14.04.2009
comment
GETNames и GETValues ​​должны дать вам подсказку, что вы НЕ можете изменить перечисление с помощью этих методов... - person Ole Albers; 19.10.2012