RJ Cuthbertson
RJ Cuthbertson

Reputation: 1518

Overriding a Method with a Parameter of Varying Type

Given the following example:

class A { }

class B : A { }

class C : A { }

I'd like all inherited classes to implement a function which performs a comparison of data specific to separate instances of these classes. The problem I'm facing is that one of the parameters is the same type as the type of the class that is implementing this method. Something like:

class A
{
    public virtual void CompareSomeData(A instanceOfA) { }
}

class B : A
{
    public override void CompareSomeData(B instanceOfB)
    {
        base.CompareSomeData(instanceOfB);
    }
}

class C : A
{
    public override void CompareSomeData(C instanceOfC)
    {
        base.CompareSomeData(instanceOfC);
    }
}

Obviously you can't override methods in C# in this manner. So what is the best pattern?

public virtual void CompareSomeData(object instance, Type typeOfInstance)
{
    // Cast the object to an instance of the type here?
}

OR

public virtual void CompareSomeData(A instanceOfA, Type typeOfInstance)
{
    // Cast the object to an instance of the type here?
}

OR some other method?

The idea here is that I would like to call into these methods from a generic method with a type constraint:

private void CompareData<T>(List<T> dataOld, List<T> dataNew) where T : A
{
    foreach (T item in dataNew)
    {
        item.CompareSomeData(GetItemToCompare(dataOld));
    }
}

Edit:

The problem I still see with both my suggestions and Marc's answer is that these really aren't type safe. There's no way for me to guarantee that an object passed to B is really B without a check in the method (if (instanceOfA is B) { ... }). I don't know if there's any way around this, but I might just have to work with it and throw an ArgumentException or something.

Upvotes: 2

Views: 98

Answers (3)

umlcat
umlcat

Reputation: 4143

Quick Disclaimer (Rant):

I'm not very fond of method overloading (same identifier, different parameter's type or different parameter's qty.). And, if it get's mix with method overriding (virtual methods), things get's more complicated.

Propposed Solution:

Inverse the process. Instead of overloading or overriding the class that handles the parameter, overload or override the class that is a parameter.

public abstract class ParameterDataClass
{
  public abstract bool CompareTo(StreamClass Stream);
} // class ParameterDataClass

public /* concrete */ class DataComparerClass
{
  internal /* nonvirtual */ void InternalComparison(string Data) { ... }

  public /* nonvirtual */ bool CompareThis(ParameterDataClass Data)
  {
    bool Result = false;

    if (Data != null)
    {
       Result = Data.CompareTo(this);
    }
  } // bool CompareThis(...)
} // class StreamClass

public /* concrete */ class CustomerClass: StreamDataClass
{
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public DateTime BirthDate { get; set; }

  public override bool CompareTo(StreamClass Stream)
  {
    bool Result = false;

    if (Stream!= null)
    {
      string Temp = "{" +
        FirstName.ToString() + "," +
        LastName.ToString() + "," +
        BirthDate.ToString() + "}"
      Result = Data.CompareTo(Temp);
    }
  } // bool CompareTo(...)

  return Result;
} // class CustomerClass

public /* concrete */ class AreaClass: StreamDataClass
{
  public int Top { get; set; }
  public int Left { get; set; }
  public int Width { get; set; }
  public int Height { get; set; }

  public override bool CompareTo(StreamClass Stream)
  {
    bool Result = false;

    if (Stream!= null)
    {
      string Temp = "{" +
        Top.ToString() + "," +
        Left.ToString() + "," +
        Width.ToString() + "," +
        Height.ToString() + "}"
      Result = Data.CompareTo(Temp);
    }
  } // bool CompareTo(...)

  return Result;
} // class AreaClass

public /* concrete */ class IntegerClass: StreamDataClass
{
  public int RealVaLue { get; set; }

  public override bool CompareTo(StreamClass Stream)
  {
    bool Result = false;

    if (Stream!= null)
    {
      Result = Data.CompareTo(RealValue.ToString());
    }
  } // bool CompareTo(...)

  return Result;
} // class IntegerClass

I don't remember the name of this technique, "Visitor Pattern", "Dispatch", whatever.

Hope it helps.

P.S. Do not forget to give an open tuna can for the kitty

Upvotes: 1

Ben Aaronson
Ben Aaronson

Reputation: 6975

It's ugly, but one way to ensure type safety without casting or requiring classes to know about the classes which inherit from them would be:

interface IDataComparer<T>
{
    void CompareSomeData(T instance);
}

class A : IDataComparer<A>
{
    public virtual void CompareSomeData(A instanceOfA) { }
}

class B : A, IDataComparer<B>
{
    public override void CompareSomeData(A instanceofA)
    {
        throw new ArgumentException("...");
    }

    public virtual void CompareSomeData(B instanceOfB)
    {
        base.CompareSomeData(instanceOfB);
    }
}

class C : A, IDataComparer<C>
{

    public override void CompareSomeData(A instanceofA)
    {
        throw new ArgumentException("...");
    }

    public virtual void CompareSomeData(C instanceOfC)
    {
        base.CompareSomeData(instanceOfC);
    }
}

Then in your CompareData<T> method, you would use the constraint T : IDataComparer<T>

Upvotes: 1

Marc Gravell
Marc Gravell

Reputation: 1062502

You cannot change the signature when overriding, but you can overload the method while overriding in parallel, for example:

class A {
    public virtual void CompareSomeData(A instanceOfA) { }
}

class B : A {        
    public void CompareSomeData(B instanceOfB) {
        // ...
    }
    public override void CompareSomeData(A instanceOfA) {
        // ...
    }
}

Note that it would be common here for one of these two methods to call into the other (with the logic in one place only) - but which should call which is implementation-specific.

Note also that if you are only changing the return type, you need to do it a different way:

class A {
    public bool CompareSomeData(A instanceOfA) {
        return CompareSomeDataImpl(instanceOfA);
    }

    protected virtual bool CompareSomeDataImpl(A instanceOfA)
    { return true; }
}

class B : A {
    public new string CompareSomeData(A instanceOfA) {
        // ...
    }
    protected override bool CompareSomeDataImpl(A instanceOfA) {
        // ...
    }
}

Upvotes: 2

Related Questions