C# 在运行时创建/修改枚举
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/746128/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Creating / Modifying Enums at Runtime
提问by Eric Anastas
I'm creating a program where the user has the option of creating their own custom properties that will ultimately be displayed in a PropertyGrid
. Right now I don't want to mess with custom editors, so I'm only allowing primitive type properties (string
, int
, double
, DateTime
, bool
etc.) that the PropertyGrid
already has built in editors for.
我正在创建一个程序,用户可以选择创建自己的自定义属性,这些属性最终将显示在PropertyGrid
. 现在我不想用自定义编辑器的一塌糊涂,所以我只允许基本类型的属性(string
,int
,double
,DateTime
,bool
等)的PropertyGrid
已经具有内置的编辑器。
However, I also want to give the user the option to create multiple choice properties where they can defined a list of possible values which in turn will show up as a drop down list in the PropertyGrid
.
但是,我还想为用户提供创建多项选择属性的选项,他们可以在其中定义可能值的列表,这些值将在PropertyGrid
.
When I hard code an Enum
in my code the property grid automatically shows properties of that enum
as a drop down list. But can I create and or modify an enumeration at runtime so the user could add another property option, and go back to the PropertyGrid
and see their new option in a drop down?
当我Enum
在代码中硬编码 an 时,属性网格会自动将其属性显示enum
为下拉列表。但是我可以在运行时创建和/或修改枚举,以便用户可以添加另一个属性选项,然后返回到PropertyGrid
下拉列表中查看他们的新选项吗?
Update
更新
Considering Patricks comment, I'm thinking that Enum
s are not the right way to go in this case. So instead how can I use a list of strings to populate a drop down in a PropertyGrid
item? Would that require a custom editor?
考虑到 Patricks 的评论,我认为Enum
在这种情况下 s 不是正确的方法。那么我如何使用字符串列表来填充项目中的下拉列表PropertyGrid
?这需要自定义编辑器吗?
采纳答案by Nicolas Cadilhac
The answer is in a simple class: TypeConverter. (and yes, enums are not suitable here).
答案就在一个简单的类中:TypeConverter。(是的,枚举不适合这里)。
Since I have not a lot of details, I will assume that you have a PropertyGrid "linked" to a target instance by the SelectedObject property and that your target instance implements ICustomTypeDescriptor so that you can add properties (i.e. PropertyDescriptors) at runtime. I don't know your design but if you are not doing like this, I advise you to have a look at it.
由于我没有太多细节,我将假设您有一个通过 SelectedObject 属性“链接”到目标实例的 PropertyGrid,并且您的目标实例实现了 ICustomTypeDescriptor,以便您可以在运行时添加属性(即 PropertyDescriptors)。我不知道你的设计,但如果你不这样做,我建议你看看它。
Now let's say that you add a string property and that you want to let your user specify a set of constraints for this property. Your UI let's the user enter a set of strings and you get a list of strings as a result. Maybe you keep a dictionary of properties in your target instance so let's assume this new list is stored there too.
现在假设您添加了一个字符串属性,并且您希望让您的用户为此属性指定一组约束。您的用户界面让用户输入一组字符串,结果您会得到一个字符串列表。也许您在目标实例中保留了一个属性字典,所以我们假设这个新列表也存储在那里。
Now, just write a new converter derived from TypeConverter (or StringConverter maybe in this example). You will have to override GetStandardValuesSupported to return true and GetStandardValuesto return the list of strings (use the context parameter to access the Instance property and its list of strings). This converter will be published by your PropertyDescriptor with the PropertyDescriptor.Converter property.
现在,只需编写一个从 TypeConverter 派生的新转换器(或者在这个例子中可能是 StringConverter)。你将不得不重写GetStandardValuesSupported返回true和GetStandardValues返回字符串列表(使用上下文参数访问实例属性和它的字符串列表)。此转换器将由您的 PropertyDescriptor 与 PropertyDescriptor.Converter 属性一起发布。
I hope this is not too nebulous. If you have a specific question on this process, just let me know.
我希望这不是太模糊。如果您对此过程有具体问题,请告诉我。
回答by Kredns
You could create code using your code, then save it to a temporary text file, and then use it. This would be slow as it involves using the HDD. I would recommend looking into reflection.
您可以使用您的代码创建代码,然后将其保存到临时文本文件中,然后使用它。这会很慢,因为它涉及使用 HDD。我会建议研究反射。
Edit:I found the perfect example in one of my books, here it is (it's pretty lengthy but if you copy it into VS, it'll make more sense).
编辑:我在我的一本书中找到了一个完美的例子,它在这里(它很长,但如果你将它复制到 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);
}
}
}
Output:
Sum of (2000) = 2001000
Looping. Elapsed milliseconds:
11468.75 for 1000000 iterations
Sum of (2000) = 2001000
Brute Force. Elapsed milliseconds:
406.25 for 1000000 iterations
输出:(2000) 之和 = 2001000
循环。已用毫秒数:
11468.75 1000000 次迭代
总和 (2000) = 2001000
蛮力。已用毫秒数:
1000000 次迭代为406.25
Hereis a link to the entire chapter if you would like more info.
如果您想了解更多信息,这里是整章的链接。
回答by Srikar Doddi
You can use Enum.GetNames() and Enum.GetValues() to retreive values and dynamically add new ones to them. although I suggest you to use a list instead of enum or rethink your design. something does not smell right.
您可以使用 Enum.GetNames() 和 Enum.GetValues() 来检索值并向它们动态添加新值。尽管我建议您使用列表而不是枚举或重新考虑您的设计。有些东西闻起来不对。
回答by Srikar Doddi
The typical engineering solution to your problem is to use to maintain the list as reference data in your database. In general enums are intended to be constants defined at compile time, and their modification in later released of code is discouraged (let alone runtime), as it can cause side effects in switch statements.
解决您问题的典型工程解决方案是使用该列表作为数据库中的参考数据进行维护。一般来说,枚举是在编译时定义的常量,并且不鼓励在以后发布的代码中修改它们(更不用说运行时了),因为它会导致 switch 语句中的副作用。