【问题标题】:Alternative to using Marker Interfaces to test for derived generic classes?替代使用标记接口来测试派生的泛型类?
【发布时间】:2013-09-09 13:37:45
【问题描述】:

我有一个应用程序,它使用来自一个公共基类的多种类型的派生类,并且一些派生类使用泛型。应用程序经常需要遍历派生类的集合并识别特定的类类型以进行特殊处理。

问题是如何在不知道类型的情况下以简洁优雅的方式测试基类的特定泛型衍生物(因为它经常在项目中完成)?

'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);
            }
        }
    }

有没有人对如何解决这个问题有更好的想法?

谢谢。

【问题讨论】:

  • AntiPattern:“派生类的集合并标识特定的类类型进行特殊处理”。为什么类本身不实现在公共接口中定义的Process 方法?
  • @spender - 无需过多解释,这些类与分层节点框架相关,其中有许多类型的节点,每个节点都派生自一个基础节点。一些节点具有通用类型。主程序的某些部分将遍历层次结构以搜索特定类型的节点。例如,构建一个包含某些节点类型但不包含其他节点类型的菜单。

标签: c# generics interface marker-interfaces


【解决方案1】:

当我需要编写因对象类型而异的代码,并且该代码作为虚拟方法不实用时,我使用作为返回不可变信息的虚拟属性实现的标识符。这更简洁,并且不使用额外的实例存储,尽管通过类型参数初始化 MarkerType 会很好。

public class BaseClass 
{
    public abstract MarkerType { get; }  
}

public enum MarkerType { A, B, C }

【讨论】:

  • 这是一个不错的方法;它也适用于接口,特别是如果MarkerType 是一个“标志”枚举。在这种情况下,可以扩展“信息”属性可以报告的内容列表,而无需更改任何现有实现(有时可能需要添加标志以同时包含 CanXCannotX 标志如果能够做到X 可以早于标志的存在)。
  • 感谢 Frank Hileman 的回复。我已将此标记为已接受的答案,因为它确实回答了我的问题,但这对我的应用程序来说并不理想。该应用程序使用 MEF 插件,因此枚举名称必须集中在某个地方,而标记接口可以在插件中分配为分布式名称。太糟糕的枚举不能被继承,否则这会很有意义。在这种情况下,我想我会坚持使用标记接口。
  • 您好,您不需要使用枚举;它必须是一个不可变的标识符(如接口定义)。在您的情况下,静态只读 GUID 可以工作,或者创建一个唯一 id 的集中式分发器:每次获取新 id 时都会通过 Interlock 增加一个静态 uint。具有确保唯一性的命名约定(例如命名空间前缀)的常量字符串也可以使用。接口也可以工作,但它并不是接口的真正首选用法。
【解决方案2】:

我已经解决这个问题一段时间了,我得出的结论是代码从一开始就结构很差。如果您在BaseClass 中有基本级别的实现,则应该通过使用IBaseClass 的策略模式来实现。这样,每件事都有一个标准化的解决方法。

在您的示例中,您将.Title 作为specialItem 的属性。我认为这对于您的所有 MEF 导出类都很常见。因此,这应该在IBaseClass 内标准化。然后,您可以使用合同名称分离您的合同,或者通过将ExportMetaData 属性添加到您的导出并使用延迟加载。

IBaseClass:

/// <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 时,通常需要从头开始重建插件结构;为了使它流畅,优雅。

【讨论】:

    【解决方案3】:

    为什么不这样做:

    item.GetType().GetGenericTypeDefinition().Equals(typeof(MyDerivedGenericClass<>))
    

    【讨论】:

      猜你喜欢
      • 2019-10-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-15
      • 2014-03-22
      • 1970-01-01
      相关资源
      最近更新 更多