【问题标题】:How to use specific interface and not general interface in C#?如何在 C# 中使用特定接口而不是通用接口?
【发布时间】:2014-01-28 09:32:16
【问题描述】:

简介

假设我有一个类组装流水线。组装流水线包含多个步骤。每个步骤都可以执行:

public interface IStep
{
    IContext Execute(IContext context);
}

每个步骤都有一个特定的 IContext,其中包含一些关于要处理什么的信息。

组装流水线包含多个步骤。它们可以通过在组装流水线上调用 Execute 来执行:

public class Assemblyline
{
    private readonly List<IStep> _steps;

    public Assemblyline(List<IStep> steps)
    {
        _steps = steps;
    }

    public void Execute(IContext context)
    {
        foreach (var step in _steps)
        {
            step.Execute(context);
        }
    }
}

我有一个通用的 IContaxt 接口:

public interface IContext
{
    string Id { get; set; }
}

以及具体的上下文:

public interface IAbcContext : IContext
{
    string Abc { get; set; }
}

public interface IDefContext : IContext
{
    string Def { get; set; }
}

public class XyzContext : IAbcContext, IDefContext
{
    public string Id { get; set; }

    public string Abc { get; set; }

    public string Def { get; set; }
}

上下文可以通过特定的步骤来处理:

public class AbcStep : IStep
{
    public IContext Execute(IContext context)
    {
        var abcContext = (IAbcContext) context;
        abcContext.Abc = "some value for Abc";
        return abcContext;
    }

    public string Id { get; set; }
}


public class DefStep : IStep
{
    public IContext Execute(IContext context)
    {
        var defContext = (IDefContext)context;
        defContext.Def = "some value for Def";
        return defContext;

    }

    public string Id
    {
        get; set; }
}

这就是我可以执行代码的方式:

internal class Program
{
    private static void Main()
    {
        var steps = new List<IStep>();
        steps.Add(new AbcStep());
        steps.Add(new DefStep());

        var assemblyline = new Assemblyline(steps);

        var context = new XyzContext {Id = "1", Abc = "blabla", Def = "other value"};
        assemblyline.Execute(context);
    }
}

问题

在每个特定步骤中,我必须将 IContext 转换为我真正想要的类。看到这一行:

var abcContext = (IAbcContext) 上下文;

public class AbcStep : IStep
{
    public IContext Execute(IContext context)
    {
        var abcContext = (IAbcContext) context;
        abcContext.Abc = "some value for Abc";
        return abcContext;
    }

    public string Id { get; set; }
}

我希望我可以离开演员阵容,然后这样做:

public class AbcStep : IStep
{
    public IContext Execute(IAbcContext context)
    {
        context.Abc = "some value for Abc";
        return context;
    }

    public string Id { get; set; }
}

但后来我收到了这条消息:

接口成员'Execute(IContext)'未实现。

无论如何我可以删除演员表吗?也许使用泛型或其他东西?

【问题讨论】:

  • 你的问题不在于演员本身(在很多情况下都会失败)。这整个事情似乎没有明确的概念。
  • 某事,某处,将不得不做这个演员或另一个。例如。如果您在其上下文中创建了IStep 泛型,那么您将 a) 必须为IStep 引入一个新的非泛型基础来实现,以便您仍然可以将所有它们(尽管它们具有不同的泛型类型)存储在一个单个 List&lt;T&gt; 和 b) 必须先将它们转换为适当的类型,然后才能调用 Execute 方法。
  • 看了几次后我不太明白,但一个明显的一点是你必须投射的原因是因为你尝试使用的方法不是你的一部分IContext 接口。而且您似乎在不同的实现中有不同的方法。为什么你不能标准化你的界面?
  • 添加接口方法的特定实现将解决命名冲突。你没有。
  • 您可以为 IContext 实现 SetValue - 也许这会有所帮助。然后上下文本身必须知道如何处理它。

标签: c# generics interface casting


【解决方案1】:

是的,可以使用泛型和一些继承(代码中的 cmets):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Test
{
    public interface IStep
    {
        IContext Execute(IContext context);
    }

    // Make a generic version also
    public interface IStep<T> : IStep where T : IContext
    {
        T Execute(T context);
    }

    public class Assemblyline
    {
        private readonly List<IStep> _steps;

        public Assemblyline(List<IStep> steps)
        {
            _steps = steps;
        }

        public void Execute(IContext context)
        {
            foreach (var step in _steps)
            {
                step.Execute(context);
            }
        }
    }

    public interface IContext
    {
        string Id { get; set; }
    }

    public interface IAbcContext : IContext
    {
        string Abc { get; set; }
    }

    public interface IDefContext : IContext
    {
        string Def { get; set; }
    }

    public class XyzContext : IAbcContext, IDefContext
    {
        public string Id { get; set; }

        public string Abc { get; set; }

        public string Def { get; set; }
    }

    // This will do the casting for you
    public abstract class BaseStep<T> : IStep<T> where T : IContext
    {
        public abstract T Execute(T context);

        public IContext Execute(IContext context)
        {
            return (IContext)Execute((T)context);
        }

    }

    // Now less code for you:
    public class AbcStep : BaseStep<IAbcContext>
    {
        public override IAbcContext Execute(IAbcContext context)
        {
            var abcContext = context;
            abcContext.Abc = "some value for Abc";
            return abcContext;
        }

        public string Id { get; set; }
    }


    public class DefStep : BaseStep<IDefContext>
    {
        public override IDefContext Execute(IDefContext context)
        {
            var defContext = context;
            defContext.Def = "some value for Def";
            return defContext;

        }

        public string Id
        {
            get;
            set;
        }
    }

    internal class Program
    {
        private static void Main()
        {
            var steps = new List<IStep>();
            steps.Add(new AbcStep());
            steps.Add(new DefStep());

            var assemblyline = new Assemblyline(steps);

            var context = new XyzContext { Id = "1", Abc = "blabla", Def = "other value" };
            assemblyline.Execute(context);
        }
    }

}

【讨论】:

  • 顺便说一句,如果您向它传递一个未实现步骤所需接口的上下文,这将变得很糟糕。您可以跳过基类中没有所需接口的步骤: if(context is T) {return (IContext)Execute((T)context);} else {return;}
  • 很好的解决方案!谢谢。
【解决方案2】:

这不一定是你的问题,但它是我的,你的症状看起来一样。

不要尝试在静态类型语言中创建自己的动态类型系统。

铸造不好,因为它会在执行时失败,而不是在编译时。

每个模块都必须依赖于它实际需要的接口,而不是依赖于其他任何东西。使用您的示例:

interface IXyzStepWork
{
    void DoXyz();
}

interface IAbcStepWork
{
    void DoAbc();
}

interface IAbcXyzWork : IAbcStepWork, IXyzStepWork
{
}

class AbcStep
{
    private readonly IAbcStepWork work;

    public AbcStep(IAbcStepWork work)
    {
        this.work = work;
    }

    public void Execute()
    {
        work.DoAbc();
    }
}

class AbcXyzStep
{
    private readonly IAbcXyzWork work;

    public AbcXyzStep(IAbcXyzWork work)
    {
        this.work = work;
    }

    public void Execute()
    {
        work.DoAbc();
        work.DoXyz();
    }
}

接下来你可以编写IAssemblyLineItem,通过“step-classes”实现它并创建AssemblyLine,它将使用它。

是的,它很简单,很明显,不使用泛型。对我来说,这才是未来发展的正确方向。

UPD:如何在您的示例中创建单个装配线。

interface IAssemblyLineItem
{
    void Execute();
}

class AssemblyLineService
{
    public void ExecuteAll(IEnumerable<IAssemblyLineItem> items)
    {
        foreach (var item in items)
        {
            item.Execute();
        }
    }
}

通过“step-classes”实现IAssemblyLineItem,它会起作用。

及用法:

class Program
{
    static void Main(string[] args)
    {
        AbcXyzWork work = new AbcXyzWork();

        AbcStep abcStep = new AbcStep(work);

        AbcXyzStep abcxyzStep = new AbcXyzStep(work);

        AssemblyLineService line = new AssemblyLineService();

        line.ExecuteAll(new IAssemblyLineItem[] { abcStep, abcxyzStep });

        Console.ReadLine();
    }
}

注意:Program.Main 取决于系统的每个类 - 正如在您的初始示例中所做的那样。

【讨论】:

  • 我也在考虑这个问题。这导致更多的代码。我必须创建特定的组装流水线,而不是一个通用的。
猜你喜欢
  • 1970-01-01
  • 2011-01-22
  • 2019-06-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多