【问题标题】:Can an enum return a new instance in C#?枚举可以在 C# 中返回一个新实例吗?
【发布时间】:2011-05-17 22:12:01
【问题描述】:

大家好!

我会尽量简化我的问题:我有一个enum 来选择我应该使用哪个ObjTypeObjTypeAObjTypeB 都继承自ObjType)。所以我创建了一个方法来扩展给定的enum,以便根据enum中的选定属性返回一个新实例,如下代码所示。我认为它或多或少有点像工厂设计模式。到目前为止一切都很好,但最终,就像在MyClass 类中一样,我可能会尝试创建ObjTypeAObjTypeBn 实例,但每次调用时我都必须面对if 声明GetObjTypeInstance() 方法。所以:

  • enum 能否返回一个实例,例如:public enum EObjType { ObjTypeA = new ObjTypeA(), ObjTypeB = new ObjTypeB() }?实际上,最好将一些GetInstance() 方法附加到ObjTypeAenum 中的ObjTypeB 选项。如果有办法做到这一点,我该怎么做?这样做我会避免在每个 while 步骤中使用这些 if 语句。
  • 还有其他(更好的)方法可以解决这个问题(如果您理解我的问题...)?怎么样?

提前致谢!

按照示例代码:

public static class EObjTypeExt
{
    public static ObjType GetObjTypeInstance(this EObjType ot)
    {
        if (ot == EObjType.ObjTypeA)
        {
            return new ObjTypeA();
        }
        else if (ot == EObjType.ObjTypeB)
        {
            return new ObjTypeB();
        }
        throw new ArgumentOutOfRangeException("unrecognized type!");
    }
}

public enum EObjType { ObjTypeA, ObjTypeB }

public class MyClass
{
    ObjType[] obj { get; set; }

    public MyClass(EObjType otEnum, int n)
    {
        this.obj = new ObjType[n];
        int i = 0;
        while (i < n)
        {
            this.obj[i] = otEnum.GetObjTypeInstance();
            i++;
        }
    }
}

【问题讨论】:

  • 为什么要避免示例代码中的 if 语句?
  • 如果n足够大(这通常是我的问题),那么代码会变得很慢。
  • 您的意思是if (ot == EObjType.ObjTypeA) 支票?它们在对象分配旁边根本不重要。
  • Girardi - “变得很慢”:你不会写那么多类。
  • @Rup 是的,也许...我没有测试它,我想知道一个显着的高点 n 会通过大量的 if 语句。但实际上,new ObjTypeA() 或 B 不会花费太多时间...

标签: c# enums factory


【解决方案1】:

你必须在某处字节这个苹果。

也许用switch 语句替换if/elseif 链,它们非常适合枚举。

【讨论】:

  • 不过,对于像这样的小枚举,如果 switch 不只是编译为两个 if,我会感到惊讶。
  • 开关也可以编译成字典。不是我们的业务/问题。唯一的标准是可读性。
  • @Danny Varod:“......仍然不是干净的代码”真的吗?您生活在哪个世界中,switch 语句隐含地意味着代码不好?您认为工厂如何运作?
  • 我不明白你为什么必须来处理这个问题。在我看来,它是一个使用多态重构应用替换条件的经典示例。这有效地消除了对任何 if/switch 逻辑的需求。
【解决方案2】:

我不会使用enum,而是使用一个看起来像枚举的类:

public class EObjType {
    public static readonly EObjType ObjTypeA = new EObjType(() => (ObjType)(new ObjTypeA));
    public static readonly EObjType ObjTypeB = new EObjType(() => (ObjType)(new ObjTypeB));

    private readonly Func<ObjType> generator;
    private EObjType(Func<ObjType> generator) {
        this.generator = generator;
    }

    public ObjType GetInstanceOfObjType() {
        return generator();
    }
}

然后你就可以像使用枚举一样使用它了。

EObjType otEnum = EObjType.ObjTypeA;
ObjType obj = otEnum.GetInstanceOfObjType();

【讨论】:

  • 不是每次我做EObjType otEnum = EObjType.ObjTypeA 或 B 时都会创建一个 ObjTypeA 和 B 的实例吗?
  • 不,它使用 delagate 来推迟 ObjType 实例的创建,直到您调用 GetInstanceOfObjTypeEObjType 的两个实例将在第一次访问 EObjType 时创建,但不会创建其他实例。然后,那些EObjType 实例可以在没有任何ifswitchDictionary 的情况下创建适当的ObjType
【解决方案3】:

您需要使用工厂或其他创建设计模式。

例如,您可以保存一个从枚举键到类型值的字典,以使用选定的枚举值获取所需的类类型。然后使用反射创建一个接收类型的新实例(对象)。

使用工厂类的静态构造函数初始化静态字典的值。您可以手动输入值,或者更好的是,从配置文件中加载可能的值。

【讨论】:

  • 对不起,我不是专家,你能说得更准确点吗?谢了!
  • 关于哪一部分?试着用谷歌搜索你不明白的东西。
  • 字典查找和反射?这不是比他已经拥有的要慢吗?
  • 如果他预加载了程序集,或者它已经在内存中,那么没有。所有这些都可以通过工厂类的静态构造函数在启动时完成。
  • @Danny:我不知道您所说的“字典值”是什么意思(我只知道 Dictionary 类)。而且我不知道如何通过反射创建实例,虽然我已经搜索过,有人说这样做不好......
【解决方案4】:

我不确定我是否真的提倡这种方法,但是您可以调用枚举 ToString() 方法,将其视为您的类名并使用反射来实例化该方法的对象输入。

这样做的一个好处是您可以一次反射并获取类型,然后在循环中调用构造函数 n 次。

【讨论】:

  • 你的意思几乎是 @shsmith 所做的,在 MyClass 中使用:var obj = Activator.CreateInstance(null, otEnum.ToString());
  • @Girardi - 是的,类似的东西。我写这样的代码已经有 2 年了,所以我没有任何方便的示例。
【解决方案5】:

正如 Danny Varod 所指出的,将您的枚举值映射到它们的类型(或创建这些类型的函数)的字典可以让您避免使用 if 语句。由于枚举实际上只是一个整数,因此数组会更节省内存和时间,但可读性在这里可能是最重要的。

【讨论】:

  • 我同意,可读性在这里是最重要的。字典性能应该接近 Array 的。
  • @Danny:Dictionary 和 Array 在性能上实际上有很大不同,尽管它们都是 O(1) 操作。几年前,当我测试时,字典读取与 200 次方法调用相当。这可能在 3.5 后的字典中发生了变化,并且肯定取决于其他因素,例如您的密钥类型。综上所述,这只会在非常紧密的循环中起作用。
【解决方案6】:

您可以创建一个工厂,允许注册映射到您的枚举的函数,您可以使用某种注册过程来注册您的不同枚举

public class ObjectFactory
{
    private readonly Dictionary<MyObjectType, Func<MyObject>> _store = new Dictionary<MyObjectType, Func<MyObject>>();

    public void Register<T>(MyObjectType type) where T: MyObject, new()
    {            
        this.Register(type, () => new T());
    }

    public void Register(MyObjectType type, Func<MyObject> factory)
    {
        _store.Add(type, factory);
    }

    public MyObject CreateInstance(MyObjectType type)
    {
        Func<MyObject> factory;
        if(_store.TryGetValue(type, out factory))
        {
            return factory.Invoke();
        }
        return null;
    }
}

public enum MyObjectType { A, B }

public class MyObject {}

public class MyObjectA : MyObject {}
public class MyObjectB : MyObject {}

用法如下

var factory = new ObjectFactory();
factory.Register<MyObjectA>(MyObjectType.A);
factory.Register<MyObjectB>(MyObjectType.B);

var a = factory.CreateInstance(MyObjectType.A);
var b = factory.CreateInstance(MyObjectType.B);

Assert.IsInstanceOf(typeof(MyObjectA), a);
Assert.IsInstanceOf(typeof(MyObjectB), b);

【讨论】:

  • 很好,但这是一种改进吗?可读性?可扩展性(您仍然需要那些注册调用)?
  • @Henk 好点...它只增加了基于外部条件注册映射的能力,而且映射可以在多个地方定义,有点像 IOC 容器
  • 我认为这个解决方案或多或少类似于@Danny 的解决方案,不是吗?
  • @Girardi,是的,只是缺少对象工厂的静态实现
【解决方案7】:

您可以使用 Activator.CreateInstance。

public class ObjType {}
public class ObjTypeA : ObjType {}
public class ObjTypeB : ObjType {}
public enum EObjType { ObjTypeA, ObjTypeB }

public static class EObjTypeExt
{
    public static ObjType GetObjTypeInstance( EObjType ot)
    {
        object o = Activator.CreateInstance(null,ot.ToString());
        return (ObjType)o;
    }
}

【讨论】:

  • 它抛出TypeLoadException,除非我写Activator.CreateInstance(null, "Namespace." + ot.ToString()),其中Namespace是类所在的命名空间
  • 即使我这样做,它也会抛出InvalidCastException: Unable to cast object of type 'System.Runtime.Remoting.ObjectHandle' to type 'Namespace.ObjType'.
猜你喜欢
  • 1970-01-01
  • 2017-04-01
  • 1970-01-01
  • 2020-04-07
  • 2017-03-31
  • 2018-10-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多