1

I've read other threads and Eric Lippert's posts on the subject, but haven't seen this exact situation addressed anywhere. C# optional parameters on overridden methods Optional parameters and inheritance

I'm trying to implement the following situation:

public class BaseClass
{//ignore rest of class for now
  public void DoThings(String str)
  {
    //dostuff
  }
}
public class DerivedClass: BaseClass
{//ignore rest of class for now
  new public void DoThings(String str, Int32 someint = 1)
  {
    //dostuff but including someint, calls base:DoThings in here
  }
}

When I do this the compiler gives me the warning in the subject line that I do not need to use the new keyword because the method does not hide the inherited method. However I do not see a way to call the base method from the object instance, so it looks hidden to me.

I want it to actually be hidden. If it is not hidden, there is potential for some other user to some day call the base method directly and break the class (it involves thread safety).

My question is, does the new method actually hide the inherited method (compiler is wrong?) or is the compiler correct and I need to do something else to hide the original method? Or is it just not possible to achieve the desired outcome?

Plaje
  • 65
  • 9
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/250541/discussion-on-question-by-plaje-cs0109-the-member-member-does-not-hide-an-in). – blackgreen Dec 19 '22 at 23:26

2 Answers2

7
  • void DoThings(String str) accepts a single parameter
  • void DoThings(String str, Int32 someint = 1) accepts two parameters

=> the methods are distinct, unrelated methods, which incidentally share the name.

Default parameters are inserted at the call-sites during compilation.

Here is one possible solution:

public class BaseClass
{
  public virtual void DoThings(String str)
  {
    //dostuff
  }
}
public class DerivedClass: BaseClass
{
  public override void DoThings(String str)
  {
    DoThings(str, 1); // delegate with default param
  }

  public void DoThings(String str, Int32 someint)
  {
    //dostuff
  }
}

Note that new makes it possible to call base classes' virtual methods in the first place by having a reference with static type of the base class (e.g. by casting it to the base class):

public class Test
{
    public static void Main()
    {
        var obj = new DerivedClass();
        BaseClass baseObj = obj;
        
        obj.DoThings("a");
        baseObj.DoThings("b");
        ((BaseClass)obj).DoThings("c");
    }
}

class BaseClass
{
  public void DoThings(String str)
  {
    Console.WriteLine("base: " + str);
  }
}

class DerivedClass: BaseClass
{
  new public void DoThings(String str, Int32 someint = 1)
  {
    Console.WriteLine("derived: " + str);
    base.DoThings(str);
  }
}

Output:

derived: a
base: a
base: b
base: c

If you want callers to never call the overridden method of a base class, mark it virtual and override it (like already shown at the top of this answer):

public class Test
{
    public static void Main()
    {
        var obj = new DerivedClass();
        BaseClass baseObj = obj;
        
        obj.DoThings("a");
        baseObj.DoThings("b");
        ((BaseClass)obj).DoThings("c");
    }
}

class BaseClass
{
  public virtual void DoThings(String str)
  {
    Console.WriteLine("base: " + str);
  }
}

class DerivedClass: BaseClass
{
  // "hide" (override) your base method:
  public override void DoThings(String str)
  {
    // delegate to method with default param:
    this.DoThings(str);
  }
  
  public void DoThings(String str, Int32 someint = 1)
  {
    Console.WriteLine("derived: " + str);
    base.DoThings(str);
  }
}

Output:

derived: a
base: a
derived: b
base: b
derived: c
base: c

After discussion in the comments: you do not want to use inheratince here, but rather opt for compisition.

The code could look like the following:

public class Test
{
    public static void Main()
    {
        var obj = new DerivedClass(new BaseClass());
        
        obj.DoThings("a");
        // baseObj.DoThings("b"); // not accessible
        // ((BaseClass)obj).DoThings("c"); // InvalidCastException!
    }
}

class BaseClass
{
  public void DoThings(String str)
  {
    Console.WriteLine("base: " + str);
  }
}

class Wrapper
{
  private BaseClass original;

  public Wrapper(BaseClass original) {
    this.original = original;
  }

  public void DoThings(String str, Int32 someint = 1)
  {
    Console.WriteLine("wrapped: " + str);
    original.DoThings(str);
  }
}

Output:

base: a
wrapped: a
knittl
  • 246,190
  • 53
  • 318
  • 364
  • So they are different signatures, but how do I call derivedclass.DoThings(somestring) and have it use the base version? The second method can be called with just the first parameter same as the first method. More importantly, how do I hide the first method without hiding the single-parameter option of the second method? – Plaje Dec 19 '22 at 19:53
  • 1
    I don't know if casting your object to a BaseClass to call the correct method would work. Try `((BaseClass)myDerivedObj).DoThings(...)`... – K. A. Dec 19 '22 at 19:56
  • @K.A. I'll have to look into that, there might be something to that. – Plaje Dec 19 '22 at 20:04
  • @knittle I didn't know that about the compiler, thanks for adding that extra bit of information. – Plaje Dec 19 '22 at 20:19
  • @knittle Saw your edit, so the casting issue will be a problem then. The original class is SerialPort :) So unfortunately I cannot modify it myself. I'll look into some possible alternative designs, mad has suggested some design possibilities. Unfortunately I think I have to rewrite my entire class now :( – Plaje Dec 19 '22 at 20:25
  • 4
    @Plaje perhaps inheritance is not the right approach and you are actually looking for _composition_? Wrap the class in an _adapting_ class and expose your new interface. If your adapter does not expose the wrapped class, callers have no way of accessing it or its methods; the only possible way is to call it through methods of the adapter. – knittl Dec 19 '22 at 20:26
  • @knittl Yep, in the end those guys were correct, I will have to keep an actual base object inside my own class and hide it entirely. It's a shame since I was hoping to keep the original class. Your solution almost worked if there was a way to prevent users from casting into serial port. – Plaje Dec 19 '22 at 20:29
  • 1
    @Plaje if you want to expose all methods of the original class plus extra logic AND the original class implements an interface, this is very clearly the _decorator pattern_. If you want to hide certain methods, add additional methods/change method signatures or don't have a defined `interface`, it would become the _adapter pattern_. Both patterns are closely related and one might even argue that every decorator can be seen as adapter, but not the other way round. – knittl Dec 19 '22 at 20:34
  • 2
    Harr harr, _"in the end those guys were correct"_ - I'll get that framed ... :D – Fildor Dec 19 '22 at 20:35
  • Well, at this point in the design, it's no longer a "base" class. You're now dealing with the "has a" relationship. `SerialPortAdapter` *has-a* (reference to) a `SerialPort` instance. – madreflection Dec 19 '22 at 21:17
0
  1. the 'new' keyword can't be used where you used it
  2. To hide the member:
public class BaseClass
{ // ignore rest of class for now

    public virtual void DoThings(String str)
    {
        // dostuff
    }
}

public class DerivedClass: BaseClass
{ //ignore rest of class for now

    public override void DoThings(String str)
    {
        // dostuff
    }

    public void DoThings(String str, Int32 someint = 1)
    {
        // do stuff but including some int, calls base:DoThings in here
    }
}
Woody1193
  • 7,252
  • 5
  • 40
  • 90
rotabor
  • 561
  • 2
  • 10
  • 2
    *"the 'new' keyword can't be used where you used it"* - That's an imprecise statement. It *can* be used there (the "where"), but in the context of that class, it's not needed because it doesn't have anything to shadow, so the compiler is only issuing a warning for it while allowing it to compile. – madreflection Dec 19 '22 at 19:58