2

How do I pull functionality from a base class that returns the base type, into an inherited class?

I have a base class Chapter, and a child class EnhancedChapter that adds some functionality to Chapter.

Unfortunately, one base class method returns the class type. I can access this method in the child class, but it since it returns the base type, and not the child type, I'm having trouble bringing its functionality into the child class.

Here's the base class (Page is implemented elsewhere, you'll get the picture):

class Chapter
{
    public List<Page> pages;

    public Chapter()
    {
        pages = new List<Page>();
    }

    public Chapter CalculatedChapter(DateTime date)
    {
        pages.ForEach(p => p.CalculatedPage(date));
        return this;
    }
}

Here's the child class:

class EnhancedChapter : Chapter
{
    public int? PageCount()
    {
        if (pages != null) return pages.Count; else return null;
    }
}

Now, when I want to use CalculatedChapter() in a the child class, I run into issues:

EnhancedChapter enhancedChapter;
// won't work, returns Chapter not EnhancedChapter
enhancedChapter = new EnhancedChapter().CalculatedChapter(DateTime.Now);

Some options I thought of, but that didn't fit well with me: I could do a cast here, but that seems prone to errors down the road. I also thought about somehow feeding the result of CalculatedChapter() into the child class constructor, but can't figure out how to do that.

Aaron Thomas
  • 5,054
  • 8
  • 43
  • 89
  • 1
    Since `PageCount` does not use any information beyond what is in `Chapter`, I would put it *in* `Chapter` (assuming you have access to change it) or make it an extension method (if you cannot change it). – crashmstr May 12 '16 at 19:26
  • 2
    Also, why does `CalculatedChapter` return `this` (since it looks like it modifies the data). Thus `var enhChap = new EnhancedChapter(); enhChap.CalculatedChapter(DateTime.Now);`? – crashmstr May 12 '16 at 19:29
  • @crashmstr for this question, the base class cannot be altered.As for reasons why to do it that way, for one example check out cascade design pattern at http://www.dotnetcurry.com/dotnet/1092/dotnet-design-patterns. – Aaron Thomas May 12 '16 at 19:34

3 Answers3

1

I think this will do it...

abstract class ChapterBase<T> where T : ChapterBase<T>
{
    public List<Page> pages;

    public ChapterBase()
    {
        pages = new List<Page>();
    }

    public T CalculatedChapter(DateTime date)
    {
        pages.ForEach(p => p.CalculatedPage(date));
        return (T)this;
    }

}

class Chapter :ChapterBase<Chapter>
{
}

class EnhancedChapter : ChapterBase<EnhancedChapter>
{
    public int? PageCount()
    {
        if (pages != null) return pages.Count; else return null;
    }
}
wizzardmr42
  • 1,634
  • 12
  • 22
  • On positive side, good use of generics... but on negative side, you're basically altering the base class, taking the functionality out of the base class, and then adding a parent to the base class. I was more curious if I could make use of functionality without altering the base class. – Aaron Thomas May 12 '16 at 19:38
  • 1
    @AaronThomas Your only other viable option is to use extension methods. See [Fluent interfaces and inheritance in C#](http://stackoverflow.com/questions/2278781/fluent-interfaces-and-inheritance-in-c-sharp) – Conrad Frix May 12 '16 at 19:57
  • 1
    You can just make the function a generic. The advantage of this is that if you are building a whole fluid API with functions returning lots of methods, you cut down the work for implementation considerably – wizzardmr42 May 12 '16 at 20:45
0

Well if you really need to do it like this, after considering different design options, you could define hiding method in EnhancedChapter:

class EnhancedChapter : Chapter
{
    public int? PageCount()
    {
        if (pages != null) return pages.Count; else return null;
    }

    public new EnhancedChapter CalculatedChapter(DateTime date) {
        return (EnhancedChapter)base.CalculatedChapter(date);
    }
}

Then if you use it through EnhancedChapter reference - this method would be called, otherwise - base one.

Evk
  • 98,527
  • 8
  • 141
  • 191
  • @crashmstr how is it different? base.CalculatedChapter returns "this" which is always EnhancedChapter, so cast will always succeed. – Evk May 12 '16 at 19:33
0

In such a case you can use the new keyword to hide the method of the base implementation and provide a modified version. In that new method you then call the base method and cast its result to EnhancedChapter.

So for your provided example you can modify your EnhancedChapter class like this:

class EnhancedChapter : Chapter {
    public int? PageCount() {
        if (pages != null) return pages.Count; else return null;
    }

    // Note the "new" keyword, hiding the base method implementation, and the changed return type
    public new EnhancedChapter CalculatedChapter(DateTime date) {
        // Just call the method on base and cast the result
        return (EnhancedChapter)base.CalculatedChapter(date);
    }
}

edit

Your concerns about the casting are not necessary in this case. This approach basically just makes working with the derived types more comfortable.

When you'll have other types derived from Chapter and use the CalculatedChapter() method, then there are 2 options:

  1. When the type does not have its own new CalculatedChapter() method then the base method implementation is called returning a Chapter
  2. When the type has its own new CalculatedChapter() method then that method is called

Even if you derive a new class from EnhancedChapter it will still only either use the base implementation (in that case of EnhancedChapter) or can provide its own implementation, hiding the base implementation.

But it's worth noting that when you're using the derived types in fields typed as Chapter then the new CalculatedChapter() method is NOT called (since you're working with the Chapter type). See the following example:

// Create EnhancedChapter            
EnhancedChapter chapEnhanced = new EnhancedChapter();

// Store the enhanced chapter as Chapter
Chapter chapEnhancedAsChapter = chapEnhanced;

// This calls the "new" method on EnhancedChapter
chapEnhanced.CalculatedChapter(DateTime.Now);

// This calls the base method implementation directly
chapEnhancedAsChapter.CalculatedChapter(DateTime.Now);

In that case no cast is made on the second call, but this also means that you cannot do any state-changing operations specific to the derived type in that methods. For example when adding a new property to EnhancedChapter, then you should not update that property in the new CalculatedChapter() method since you cannot ensure that this method is actually used.

So in the end this approach will only work when using it to increase the comfort when working with the derived types, but you cannot use it when you're actually changing the implementation details within these different methods.

bassfader
  • 1,346
  • 1
  • 11
  • 15
  • So, you're suggesting that my idea of casting the base class type to the child class type is the best way to go? – Aaron Thomas May 12 '16 at 19:30
  • It's the way to solve the problem of the return type. This basically will ensure that when you're working with the `EnhancedChapter` type that calling this method will also return a `EnhancedChapter`. When working with other types derived from `Chapter` they either use the base implementation (returning a `Chapter`) or they can also add a new `CalculatedChapter` method with updated return type, so the casting should not be a problem. – bassfader May 12 '16 at 19:37
  • I added some more information in post in the last edit regarding the casting – bassfader May 12 '16 at 19:54