使用标记接口测试派生泛型类的替代方法
本文关键字:方法 泛型类 派生 接口测试 | 更新日期: 2023-09-27 18:30:28
我有一个应用程序,它使用来自公共基类的多种类型的派生类,并且有几个派生类使用泛型。 应用程序通常需要循环访问派生类的集合,并确定特定的类类型以进行特殊处理。
问题是如何在不知道类型的情况下,以简洁优雅的方式测试基类的特定泛型导数(因为它经常在项目中完成)?
"is"运算符不适用于此:
if (item is MyDerivedGenericClass<>) // Won't compile
为了解决此问题,我正在考虑使用空标记接口来标识集合中的特殊类。 这似乎是最干净的解决方案。
几点注意事项:
- 这是一个封闭的项目,不会是一个商业分发的库。
- 由标记接口标记的类被标记为永久唯一类型,并且该类型的所有派生类都将正确继承标记。
下面是一个示例:
public interface MarkerA { } // Empty Interface
public interface MarkerB { } // Empty Interface
public interface MarkerC { } // Empty Interface
public class BaseClass { }
public class DerivedClassA : BaseClass { }
public class DerivedClassB : BaseClass { }
public class DerivedClassC : BaseClass { }
public class DerivedSpecialClass : BaseClass { }
public class DerivedSpecialA : DerivedSpecialClass { }
public class DerivedSpecialB : DerivedSpecialClass { }
public class DerivedSpecialC : DerivedSpecialClass { }
public class DerivedSpecialGenericA<T> : DerivedSpecialClass, MarkerA { }
public class DerivedSpecialGenericB<T> : DerivedSpecialClass, MarkerB { }
public class DerivedSpecialGenericC<T> : DerivedSpecialClass, MarkerC { }
public void ProcessClasses(List<BaseClass> list)
{
// Iterate through a list of mixed classes that all derive from BaseClass
foreach (BaseClass item in list)
{
// Ideal approach, but doesn't compile:
// Try to isolate select class types to do something interesting with
if ((item is DerivedSpecialA) || (item is DerivedSpecialB) || (item is DerivedSpecialGenericB<>))
{
var specialItem = item as DerivedSpecialClass;
DoSomethingInteresting(specialItem);
Console.WriteLine(specialItem.Title);
}
// Alternative workaround that tests for the Marker instead
// Try to isolate select class types to do something interesting with
if ((item is DerivedSpecialA) || (item is DerivedSpecialB ) || (item is MarkerB))
{
var specialItem = item as DerivedSpecialClass;
DoSomethingInteresting(specialItem);
Console.WriteLine(specialItem.Title);
}
}
}
有没有人对如何处理这个问题有更好的想法?
谢谢。
当我需要编写因对象类型而异的代码,并且该代码作为虚拟方法不实用时,我使用作为返回不可变信息的虚拟属性实现的标识符。这更干净,并且不使用额外的实例存储,尽管通过类型参数初始化 MarkerType 会很好。
public class BaseClass
{
public abstract MarkerType { get; }
}
public enum MarkerType { A, B, C }
解决这个问题一段时间了,我得出的结论是,代码从一开始就结构很差。如果您在 BaseClass
中具有基础级实现,则应使用 IBaseClass
通过策略模式实现。这样,一切都有标准化的解决方法。
在您的示例中,您已.Title
作为specialItem
的属性。我认为这对于您的所有 MEF 导出类都是通用的。因此,这应该在IBaseClass
内标准化。然后,您可以使用合同名称分隔合同,或者通过向导出添加ExportMetaData
属性并使用延迟加载来分离合同。
IBase类:
/// <summary>
/// Provides mechanisms for working with BaseClasses.
/// </summary>
public interface IBaseClass
{
/// <summary>
/// Gets the title of the MEF Exported class.
/// </summary>
/// <value>
/// The title.
/// </value>
string Title { get; }
}
基类:
/// <summary>
/// Acts as a base implementation for all derived classes.
/// </summary>
public abstract class BaseClass : IBaseClass {
/// <summary>
/// Initialises a new instance of the <see cref="BaseClass"/> class.
/// </summary>
/// <param name="title">The title.</param>
protected BaseClass(string title)
{
// Set the title.
Title = title;
}
#region Implementation of IBaseClass
/// <summary>
/// Gets the title of the MEF Exported class.
/// </summary>
/// <value>
/// The title.
/// </value>
public string Title { get; private set; }
#endregion
}
然后从那里派生你的类...
派生类:
[Export("DerivedClasses", typeof(BaseClass))]
public class DerivedClassA : BaseClass
{
public DerivedClassA()
: this("DerivedClassA")
{
/* IoC Friendly Constructor */
}
/// <summary>
/// Initialises a new instance of the <see cref="BaseClass"/> class.
/// </summary>
/// <param name="title">The title.</param>
public DerivedClassA(string title) : base(title)
{
/* Construction Logic */
}
}
请注意,此处我已将合约名称添加到导出中。你的特殊类拓宽了BaseClass
的功能,并且该功能应该被划分到另一个接口中。
IDerivedSpecialBaseClass:
/// <summary>
/// Provides mechanisms for working with special BaseClasses.
/// </summary>
public interface IDerivedSpecialBaseClass : IBaseClass
{
string SpecialProperty { get; }
}
此接口继承自IBaseClass
,因为它派生自同一根。您的具体实现与上述相同,但实现IDerivedSpecialBaseClass
而不仅仅是IBaseClass
。
DerivedSpecialBaseClass:
/// <summary>
/// Acts as a base implementation for all derived special classes.
/// </summary>
public abstract class DerivedSpecialBaseClass : BaseClass
{
protected DerivedSpecialBaseClass()
: this("DerivedSpecialBaseClass")
{
/* IoC Friendly Constructor */
}
/// <summary>
/// Initialises a new instance of the <see cref="BaseClass"/> class.
/// </summary>
/// <param name="title">The title.</param>
protected DerivedSpecialBaseClass(string title) : base(title)
{
/* Constructor Logic */
}
#region Implementation of IDerivedSpecialBaseClass
/// <summary>
/// Gets the special property.
/// </summary>
/// <value>
/// The special property.
/// </value>
public abstract string SpecialProperty { get; }
#endregion
}
具体实施:
[Export("DerivedSpecialClasses", typeof(DerivedSpecialBaseClass))]
public class DerivedSpecialA : DerivedSpecialBaseClass
{
/// <summary>
/// Initialises a new instance of the <see cref="DerivedSpecialA"/> class.
/// </summary>
public DerivedSpecialA()
: this("DerivedSpecialA")
{
/* IoC Friendly Constructor */
}
/// <summary>
/// Initialises a new instance of the <see cref="DerivedSpecialA"/> class.
/// </summary>
/// <param name="title">The title.</param>
public DerivedSpecialA(string title)
: base(title)
{
/* Construction Logic */
}
#region Implementation of IDerivedSpecialBaseClass
/// <summary>
/// Gets the special property.
/// </summary>
/// <value>
/// The special property.
/// </value>
public override string SpecialProperty
{
get { return @"Hello, from DerivedSpecialA"; }
}
#endregion
}
请注意使用的不同协定名称以及导出的不同类型。如果必须使用泛型,则可以以相同的方式添加第三个接口。
接口:
/// <summary>
/// Provides mechanisms for doing stuff with things.
/// </summary>
/// <typeparam name="T">The type of things to do stuff with.</typeparam>
public interface IDerivedSpecialBaseClass<T> : IDerivedSpecialBaseClass
where T : struct
{
/// <summary>
/// Does stuff with things.
/// </summary>
/// <param name="thing">The thing.</param>
void DoStuffWith(T thing);
}
原始
/// <summary>
/// Acts as a base class for all generic special classes.
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class DerivedGenericBaseClass<T> : DerivedSpecialBaseClass, IDerivedSpecialBaseClass<T>
where T : struct
{
#region Implementation of IDerivedGenericBaseClass<T>
/// <summary>
/// Does stuff with things.
/// </summary>
/// <param name="thing">The thing.</param>
public abstract void DoStuffWith(T thing);
#endregion
}
混凝土:
[Export("DerivedSpecialGenericClasses", typeof(DerivedGenericBaseClass<>))]
public class DerivedSpecialGenericA<T> : DerivedGenericBaseClass<T>
where T : struct
{
#region Overrides of DerivedSpecialBaseClass
/// <summary>
/// Does stuff and things.
/// </summary>
/// <param name="thing">The thing.</param>
public override void DoStuffWith(T thing)
{
Console.WriteLine("Doing Stuff and Things with " + thing.GetType().Name);
}
/// <summary>
/// Gets the special property.
/// </summary>
/// <value>
/// The special property.
/// </value>
public override string SpecialProperty
{
get { return @"Hello, from DerivedSpecialGenericA"; }
}
#endregion
}
现在,在 MEF 中处理这些问题的最简单方法是创建一个适配器类来处理组合。
适配器类
/// <summary>
/// Adapter class for MEF Imported contracts, deriving from BaseClass.
/// </summary>
public class Classes
{
/// <summary>
/// Gets or sets the derived classes.
/// </summary>
/// <value>
/// The derived classes.
/// </value>
/// <remarks>
/// This list will be populated via MEF.
/// </remarks>
[ImportMany("DerivedClasses", typeof(BaseClass))]
private IEnumerable<IBaseClass> DerivedClasses { get; set; }
/// <summary>
/// Gets or sets the derived special classes.
/// </summary>
/// <value>
/// The derived special classes.
/// </value>
/// <remarks>
/// This list will be populated via MEF.
/// </remarks>
[ImportMany("DerivedSpecialClasses", typeof(DerivedSpecialBaseClass))]
private IEnumerable<IDerivedSpecialBaseClass> DerivedSpecialClasses { get; set; }
/// <summary>
/// Gets or sets the derived special generic classes.
/// </summary>
/// <value>
/// The derived special generic classes.
/// </value>
/// <remarks>
/// This list will be populated via MEF.
/// </remarks>
[ImportMany("DerivedSpecialGenericClasses", typeof(DerivedGenericBaseClass<>))]
private IEnumerable<IDerivedSpecialBaseClass> DerivedSpecialGenericClasses { get; set; }
/// <summary>
/// Initialises a new instance of the <see cref="Classes"/> class.
/// </summary>
public Classes()
{
// NOTE: This is a generic application catalogue, for demonstration purposes.
// It is likely you'd rather use a Directory or Assembly catalogue
// instead, to make the application more scalable, in line with the MEF
// ideology.
var catalogue = new ApplicationCatalog();
var container = new CompositionContainer(catalogue);
container.ComposeParts(this);
}
/// <summary>
/// Processes the classes.
/// </summary>
public void ProcessClasses()
{
foreach (var item in DerivedClasses)
{
DoSomethingInteresting(item);
}
foreach (var item in DerivedSpecialClasses)
{
DoSomethingInteresting(item);
}
foreach (var item in DerivedSpecialGenericClasses)
{
DoSomethingInteresting(item);
}
}
private void DoSomethingInteresting(IBaseClass specialItem)
{
Console.WriteLine("Something interesting has been done with: " + specialItem.Title);
}
}
请注意,您不再需要检查哪种类型是哪种类型,因为它们已被拆分为三个单独的列表,使用 MEF 合约名称将它们过滤到正确的列表中。您不应该引用适配器中的具体实现,因为您在运行时不知道它们是否存在。如果他们不这样做,您的应用程序将失败。
相反,我建议在IBaseClass
接口中添加一个ShouldProcess
标志,并且只处理将其设置为 true 的标志。
foreach (var item in DerivedSpecialGenericClasses.Where(p => p.ShouldProcess == true))
{
DoSomethingInteresting(item);
}
类似的东西。考虑分离您的关注点。MEF 不知道流经它的内容,因此您的应用程序也不知道。让它成为焦点,让插件告诉你的应用程序他们需要什么。在尽可能低的级别实施这些检查,以便可以批量处理它们。
当您不完全了解程序流、设计模式和可缩放体系结构时,使用 MEF 可能会很复杂。例如,这可以通过延迟加载来实现,并将处理检查添加为ExportMetadata
。它不会增加太多开销,但如果您没有一次性完全吞下插件,可能会更复杂。从其他可扩展性较差的合成引擎移植到 MEF 时,通常需要从头开始重建插件结构;为了使它流畅,优雅。
为什么不这样做:
item.GetType().GetGenericTypeDefinition().Equals(typeof(MyDerivedGenericClass<>))