从ID(或名称)创建对象

我有一个很多子类inheritance的抽象类:

public abstract class CrawlerBase { public abstract void Process(string url); } 

我正在研究这个循环:

 foreach (var item in result) { object crawler = null; switch (item.Type) { case "Trials": var t = new Trials(); ct.Process(item.URL); //repetitive code. break; case "Coverage": var c = new Coverage(); c.Process(item.URL); //repetitive code. break; default: break; } // crawler.Process(item.URL); } 

现在,item.type字符串将取决于需要实例化的子类。 由于我的所有子类都inheritance了我的基类,因此在每个case语句中调用.Process()会非常重复。 我想将对象“crawler”强制转换为正在实例化的子类,并在switch语句的末尾调用crawler.Process(),如注释中所示。 我怎样才能做到这一点?

有很多方法可以从字符串创建一个类型。 reflection可以是其中之一,它很 (但如果你正在处理网页请求可能你不会关心这个)但你不需要保留一长串硬编码字符串和/或有一个丑陋长开关/案例陈述。

如果速度不是问题,那么让我们从简单的开始:

 CrawlerBase crawler = (CrawlerBase)Activator.CreateInstance( Type.GetType("MyNamespace." + item.Type)); crawler.Process(item.URL); 

使用reflection您不需要切换/大小写,并且无论您拥有多少类型,您都不会更改工厂代码以适应CrawlerBase新实现。 只需添加一个新的实现et-voila。

这个怎么运作? 那么你可以从它的Type定义(使用Activator.CreateInstance )创建一个类的实例,所以问题是得到TypeType类提供了一个静态GetType方法,可用于从其(完整)名称创建Type 。 在这种情况下,我们提供一个名称空间,它必须更改为真正的名称空间,如果您将所有类保留在同一名称空间中,您可以编写如下内容:

 string rootNamespace = typeof(CrawlerBase).Namespace; 

然后是maked-up版本:

 CrawlerBase crawler = (CrawlerBase)Activator.CreateInstance( Type.GetType(rootNamespace + "." + item.Type)); crawler.Process(item.URL); 

改进1

这是一个非常天真的实现,更好的版本应该在字典中缓存Type

 private static Dictionary _knownTypes = new Dictionary(); private static GetType(string name) { string fullName = typeof(CrawlerBase).Namespace + "." + name; if (_knownTypes.ContainsKey(fullName)) return _knownTypes[fullName]; Type type = Type.GetType(fullName); _knownTypes.Add(fullName, type); return type; } 

现在你没事,你不需要管理一个(可能)很长的字符串列表,你不需要改变一个无限的开关/案例,如果你添加/删除/更改一个类型并添加一个新的Crawler所有你有要做的是派生一个新的课程。 请注意,此函数不是线程安全的,因此,如果您有多个线程,则必须添加锁以保护字典访问(或者ReadWriterLockSlim ,这取决于您的使用模式)。

改进2

下一步是什么? 如果您的要求更严格(例如关于安全性),您可能需要添加更多内容(我认为这不是您的情况,因此您甚至可能不需要类型字典)。 怎么办? 首先,您可以检查创建的类型是否来自CrawlerBase (使用IsAssignableFrom并提供更有意义的exception而不是InvalidCastException ),其次,您可以使用属性修饰类。 只会创建具有该属性的类(因此,即使标记为公共,也无法从用户创建私有实现)。 除此之外还有很多,但一个简单的实现通常足以解决这类问题(当输入不是来自用户而是来自健壮的配置文件或来自程序本身)。

这不起作用吗?

 foreach (var item in result) { CrawlerBase crawler = null; switch (item.Type) { case "Trials": crawler = new Trials(); break; case "Coverage": crawler = new Coverage(); break; default: break; } if(crawler != null) crawler.Process(item.URL); } 

使用工厂方法制作字典:

 var factories = new Dictionary> { {"Trials", () => new Trials()}, {"Coverage", () => new Coverage()}, // and so on } 

……扔掉switch

 foreach (var item in result) { factories[item.Type]().Process(item.URL); } 

你不能这样做?

  { CrawlerBase crawler = null; switch (item.Type) { case "Trials": crawler = new Trials(); break; case "Coverage": crawler = new Coverage(); break; default: break; } crawler.Process(item.URL); 

}

正如Redi指出的那样,你在任何实例上都调用了Process,因为它们都来自同一个基类。 您的交换机案例中仍然存在一些重复代码,如果您从交换机(if / else)转移到映射构造,则可以简化这些代码。

 Dictionary Types = new Dictionary { { "Trial", typeof(Trials) }, { "Coverage", typeof(Coverage) }, }; void Crawl() { string itemType = "Trial"; CrawlerBase crawler = (CrawlerBase)Activator.CreateInstance(Types[itemType]); crawler.Process("url"); } 

如果将工厂方法作为值放入字典中,也可以使用像Dennis这样的代理。