views:

92

answers:

2

I have the following 3 classes:

class Node {
    public Node Parent;

    // Edit: to clarify, each of these classes has many fields and methods
    public void Method1() { /*...*/ }
    public void Method2() { /*...*/ }
    /* ... */
    public void Method30() { /*...*/ }
}
class Element : Node { public Dictionary<string, string> Attributes; }
class Document : Element { }

I need to have a way to define ExtraNode, ExtraElement and ExtraDocument so that they are the equivalent of copying the code above, adding a few extra fields to the Node class, and prefixing all classes with "Extra", like so:

class ExtraNode { public Node Parent; public int Priority; }
class ExtraElement : ExtraNode { public Dictionary<string, string> Attributes; }
class ExtraDocument : ExtraElement { }

What is the best to achieve this? I thought about interfaces (ExtraNode : Node, IExtra), but then (ExtraElement is ExtraNode) == false.

I also thought about rewriting the classes as generics (Node<T>, Element<T>, Document<T>), then using them to define the new classes (ExtraNode : Node<MyExtraClass> and so on), but it still leads to the problem of (ExtraElement is ExtraNode) == false because (Element<MyExtraClass> is Node<MyExtraClass>) == false.

So what is the best way of achieving this, without copy / pasting the entire document and changing by hand? I will need to do this more than once.

Edit: to clarify what I need to be able to do, I need the following code to work:

// this should accept an ExtraNode, an ExtraElement or an ExtraDocument:
void doSomethingWithANode(ExtraNode extraNode) { ... }

Edit 2: Kirk's suggestion about implementing interfaces (as exemplified by Timwi's code) is impractical in my case because each class has many fields and methods (and there are about 8 classes in total), so I would have to copy each and every one of them for each 'Extra' derivate group. For example this is how the Node classes would look:

class INode {
    INode Parent { get; }
    void Method1();
    void Method2();
    /* ... */
    void Method30();
}

class ExtraNode {
    public int Priority { get; } // extra
    public INode Parent { get; } // THIS IS WRONG, THIS SHOULD BE ExtraNode NOT INode
    public void Method1() { ... } // here I have to define the entire method
    public void Method2() { ... }
    /* ... */
    public void Method30() { ... }
}

class SuperNode {
    public string Superpower { get; } // extra
    public INode Parent { get; } // THIS IS WRONG, THIS SHOULD BE SuperNode NOT INode
    public void Method1() { ... } // here I have to define the entire method AGAIN
    public void Method2() { ... } // all methods are identical to the ones in ExtraNode
    /* ... */
    public void Method30() { ... } // this is unnecessary code duplication
}

So 30 methods are duplicated every single time. And this applies for all the other classes. By this method I would duplicate so much, I'm practically duplicating all the code. It would be easier to simply copy / paste the entire class, rename and add the extra fields to them.

+1  A: 

I think Kirk Woll’s idea in the comments is pretty much the best option. I ran into a similar problem in my code, and I came up with the same idea. What you really want to be able to do is have an inheritance tree with multiple inheritance, and in C# only interfaces allow you this.

public interface INode { public INode Parent { get; } }
public interface IElement : INode { public Dictionary<string, string> Attributes { get; } }
public interface IDocument : IElement { ... }

public interface IExtraNode : INode { public int Priority { get; } }
public interface IExtraElement : IExtraNode, IElement { }
public interface IExtraDocument : IExtraElement, IDocument { ... }

Now all the properties you wanted are true:

element is INode                // check
document is IElement            // check
extraNode is INode              // check
extraElement is INode           // check
extraElement is IExtraNode      // check
extraDocument is IExtraElement  // check
extraDocument is IElement       // check
extraDocument is INode          // check

Responding to your Edit #2: Yes, you would have to duplicate some of the implementations, but not many. In particular, you don’t need to duplicate anything in ExtraNode (you can just inherit from Node). If the “extra” stuff is not much, you can duplicate only the extra stuff, i.e. inherit ExtraElement from Element (instead of from ExtraNode) etc.

Regarding the problem you mentioned here:

public INode Parent { get; } // THIS IS WRONG, THIS SHOULD BE ExtraNode NOT INode

I believe this should be IExtraNode. You can solve this by using an explicitly-implemented property (I’m assuming now that you’re not deriving ExtraNode from Node, but only to illustrate the solution):

// Real code goes here
public IExtraNode Parent { get { return ...; } }

// This calls the property above, and implements the interface property
public INode INode.Parent { get { return Parent; } }

Yet another edit: You could also declare a class called Extra which contains all the extra functionality, and have ExtraNode, ExtraElement and ExtraDocument all have a field of type Extra. You could either make this field public, or you could write one-line redirect methods for all its functionality. This is truly a minimum of duplication, given the lack of multiple inheritance.

Timwi
Please see my 2nd edit.
manixrock
@manixrock — Edited my answer.
Timwi
A: 

You could try to use a mixin-like construct to share the necessary code:

interface MNode {
  MNode Parent { get; }
}
static class MNodeCode {
  public static void Method1(this MNode self) { /*...*/ }
  public static void Method2(this MNode self) { /*...*/ }
  /* ... */
  public static void Method30(this MNode self) { /*...*/ }
}

class Node : MNode {
  public Node Parent { get { ... } }
  MNode MNode.Parent { get { return Parent; } }
}
class Element : Node { public Dictionary<string, string> Attributes; }
class Document : Element { }

class ExtraNode : MNode {
  public ExtraNode Parent { get { ... } }
  MNode MNode.Parent { get { return Parent; } }
}
class ExtraElement : ExtraNode { public Dictionary<string, string> Attributes; }
class ExtraDocument : ExtraElement { }

You can create different mixins to get a different sharing granularity.

Jordão