【问题标题】:Implementing overload methods for subtypes of an abstract class in C#在 C# 中为抽象类的子类型实现重载方法
【发布时间】:2020-01-13 17:53:08
【问题描述】:

我是 C# 新手,我真的很想为定义的抽象类的每个子类型实现特定的不同方法,但我无法弄清楚如何让编译器正确执行此操作。例如:

public abstract class MasterClass { }
public class SubClass1 : MasterClass { }
public class SubClass2 : MasterClass { }

public class SeparateClass
{
    public void HandleMasterClass(MasterClass item)
    {
       /*
       stuff generic to both subclasses...
       */
       SpecificMethod(item)
    }

    public void SpecificMethod(SubClass1 item)
    {
        //something specific to SubClass1
    }

    public void SpecificMethod(SubClass2 item)
    {
        //something specific to SubClass2
    }
}

这会在编译时返回错误,因为没有SpecificMethod(MasterClass item),但我真正想要的是它可以根据子类选择正确的方法,而不必编写单独的HandleMasterClass(SubClass1 item) 和HandleMasterClass(SubClass2 item) 方法,因为它们是几乎相同的代码

我的主要语言是 Jula,所以我非常习惯于依赖多次调度和做这种事情。我知道它在 C# 中可能不是惯用的,那么我该如何做得更好?

编辑:显示方法不是免费的,而是单独类的一部分

这里有一个更好的具体例子

public abstract class MasterClass { public abstract int Stuff(); }
public class SubClass1 : MasterClass
{
    public override int Stuff() { /*calculate and return an int*/ }
}

public class SubClass2 : MasterClass
{
    public override int Stuff() { /*calculate and return an int*/ }
}

public class MasterClassDictionary
{
    public Dictionary<int, SubClass1> subClass1Dict{get;} = new Dictionary<int, SubClass1>()
    public Dictionary<int, SubClass2> subClass2Dict{get;} = new Dictionary<int, SubClass2>()

    public void Add(MasterClass item)
    {
        int val = item.Stuff();
        AddToDict(val, item);
    }

    void AddToDict(int val, SubClass1 item) { subClass1Dict[val] = item; }
    void AddToDict(int val, SubClass2 item) { subClass2Dict[val] = item; }

}

我知道这是一个人为的例子,但它与我正在尝试做的类似。

【问题讨论】:

  • 您可以使一种方法接受“大师班”并使用原样检查或其他方式。你也可以在 handlemasterclass 中做同样的事情
  • 我没有提到,但是这些方法在一个单独的类中为此做一些事情
  • 编辑显示
  • 我个人喜欢这样做,因为我在阅读其他人的帖子时通常不会查看编辑历史记录,并且编辑说明会将您的 cmet 放在上下文中。将其视为解释编辑内容的代码注释,而不仅仅是编辑
  • 一种解决方案是将项目转换为动态(SpecificMethod((dynamic) item))。不过我更喜欢强类型的解决方案。

标签: c# overloading


【解决方案1】:

通常,您希望将特定于某个类的代码放入该类中。因此,您的抽象类将使用 abstract 关键字定义特定的方法签名,并且使用 override 关键字将实现存在于类中,如下所示:

public abstract class MasterClass {
    public abstract void SpecificMethod();
}

public class SubClass1 : MasterClass {
    public override void SpecificMethod()
    {
        //something specific to SubClass1
        // use the this keyword to access the instance

    }
}

public class SubClass2 : MasterClass {
    public override void SpecificMethod()
    {
        //something specific to SubClass2
        // use the this keyword to access the instance
    }
}

public class SeparateClass
{
    public void HandleMasterClass(MasterClass item)
    {
      /*
      stuff generic to both subclasses...
      */
      item.SpecificMethod()
    }
}

根据您的评论,这就是我在您的具体示例中实现该功能的方式,尽管它可能不符合您的要求:

public class MasterClassDictionary
{
    public Dictionary<int, SubClass1> subClass1Dict{get;} = new Dictionary<int, SubClass1>()
    public Dictionary<int, SubClass2> subClass2Dict{get;} = new Dictionary<int, SubClass2>()

    public void Add(MasterClass item)
    {
        int val = item.Stuff();

        if (item is SubClass1)
        {
            subClass1Dict[val] = item;
        }

        if (item is SubClass2)
        {
            subClass2Dict[val] = item;
        }
    }
}

【讨论】:

  • 另一种说法是抽象基类不应该知道或引用它们的子类。
  • 无法保存我的编辑(不够长)...overrides 关键字应该是override
  • 我添加了一个更具体的示例来说明为什么我不只是这样做。你能告诉我你是否还会采用这种方法吗?
  • 谢谢@GreggL,完全错过了。
  • @stords,我认为在您的具体示例中,我可能只是在 MasterClassDictionary 的 Add 方法中使用 is 关键字进行一些类型检查,以确保项目结束在正确的字典中。在不了解您的具体要求的情况下很难提出进一步的建议。
【解决方案2】:

这种情况的标准设计模式是Visitor pattern。这是一个有点复杂的模式,但基本思想是子类知道它们是什么类型,所以我们将通过一个名为“Accept”的虚拟方法调用它们,它们会将自己作为引用传回。他们回调的方法称为 Visit 并为所有可能的子类重载。这是您的示例的实现:

public abstract class MasterClass
{
    public abstract int Stuff();

    // New method that all subclasses will have to implement.
    // You could also have this be virtual with an implementation
    // for Visit(MasterClass) to provider a default behavior.
    public abstract void Accept(IVisitor visitor);
}

public class SubClass1 : MasterClass
{
    public override int Stuff() => 0;

    // We must override this even though its the "same" code in both subclasses
    // because 'this' is a reference to a different type.
    public override void Accept(IVisitor visitor) => visitor.Visit(this);
}

public class SubClass2 : MasterClass
{
    public override int Stuff() => 1;

    // We must override this even though its the "same" code in both subclasses
    // because 'this' is a reference to a different type.
    public override void Accept(IVisitor visitor) => visitor.Visit(this);
}

public interface IVisitor
{
    // Need an overload for all subclasses.
    void Visit(SubClass1 item);
    void Visit(SubClass2 item);
}

public class MasterClassDictionary
{
    public Dictionary<SubClass1, int> subClass1Dict { get; } = new Dictionary<SubClass1, int>();
    public Dictionary<SubClass2, int> subClass2Dict { get; } = new Dictionary<SubClass2, int>();

    public void Add(MasterClass item)
    {
        int val = item.Stuff();
        var visitor = new Visitor(this, val);
        item.Accept(visitor);
    }

    void AddToDict(SubClass1 item, int val) { subClass1Dict[item] = val; }

    void AddToDict(SubClass2 item, int val) { subClass2Dict[item] = val; }

    // Provides the visitor implementation that holds any state that might
    // be needed and dispatches to the appropriate method.
    private class Visitor : IVisitor
    {
        private MasterClassDictionary _parent;
        private int _value;

        public Visitor(MasterClassDictionary parent, int val)
        {
            _parent = parent;
            _value = val;
        }

        public void Visit(SubClass1 item) => _parent.AddToDict(item, _value);

        public void Visit(SubClass2 item) => _parent.AddToDict(item, _value);
    }
}

也就是说,C# 添加了带有 switch 的模式匹配,看起来更简单。唯一的缺点是它会进行更多的类型检查,如果这是在一些真正对性能敏感的代码中可能会更慢,但肯定会比使用dynamic 更快:

public void Add(MasterClass item)
{
    int val = item.Stuff();
    switch (item)
    {
        case SubClass1 i: AddToDict(i, val); break;
        case SubClass2 i: AddToDict(i, val); break;
    }
}

【讨论】:

  • 这是完美的。我正在尝试编写对性能非常敏感的代码(这是首先使用 C# 而不是 Julia 或 Python 的原因)所以我试图尽可能多地留给编译器,同时仍然为自己节省尽可能多的打字工作如我所能。我喜欢通用的 IVisitor 界面
  • @stords 如果这是 真的 性能敏感代码,我可能想以两种方式进行基准测试。访客模式显示作出交换机不分配的分配。根据场景的不同,内存压力可能是一个更大的问题,但这可以通过池化和重用访问者对象来缓解。
猜你喜欢
  • 2016-12-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多