views:

84

answers:

4

With the structure..

abstract class Unit
{
 int Id;
}

class Measure : Unit
{
 int Current;
 int Baseline;
}

class Weight : Unit
{
 int Minimum;
 int Maximum;
 int Current;
}

I basically want to add an "Add" method for adding, say, two Measures together, or adding two Weights together. But it needs to be in the Unit base class. So basically if I had

List<Units> units = new List<Unit>();
List<Units> otherUnits = new List<Unit>();

// populate units with various Measures and Weights.
// populate otherUnits with various Measures and Weights.
foreach(Unit u in units)
{
 u.Add( 
         // add something from the otherUnits collection. Typesafe, etc.
      ); 
} 

I have tried ..

public abstract T Add<T>(T unit) where T : Unit;

in the Unit class, but I get errors about how it isn't an appropriate identifier in the inherited classes when I try to fill "T" with the appropriate class. Any ideas?

+3  A: 

You need to change your Unit abstract class to take a generic type:

abstract class Unit<T>

Then you can add the Add abstract method:

void Add(T unit);

So your measurement and weight classes will now look like:

class Measure : Unit<Measure>
class Weight : Unit<Weight>

Alternatively, add the following abstract method to Unit:

abstract void Add(Unit unit);

And then you'll need to constraint this using type checking within your inheriting class:

void Add(Unit unit)
{
    if (unit.GetType() != this.GetType())
    {
        throw new ArgumentException("You can only add measurements.");
    }
}
GenericTypeTea
My Unit class has to participate in an EntityConfiguration<T>. So I cannot make it take a generic type. is there any other way? Oh, I see - you added an alternative way. I think this might work. Let me try it and find out.
Stacey
@Stacey - My alternative should work in the `Unit` class instead. So you don't need to make it abstract.
GenericTypeTea
A: 

If your hierarchy is stable enough, you can use the visitor pattern idea to come up with something like this:

abstract class Unit {
  public abstract Unit Add(Unit unit);
  public virtual Measure Add(Measure unit) { return unit; }
  public virtual Weight Add(Weight unit) { return unit; }
}

class Measure : Unit {
  public override Measure Add(Measure unit) { 
    // ...
  }
  public override Unit Add(Unit unit) {
    return unit.Add(this);
  }
}

class Weight : Unit {
  public override Weight Add(Weight unit) {
    // ...
  }
  public override Unit Add(Unit unit) {
    return unit.Add(this);
  }
}

You can even use a true visitor and separate it from the Unit class if you anticipate other behaviors to be added to the hierarchy later.

Note: if Add will change the current instance, then if should return void.

Jordão
StackOverflowException! If you didn't match types, the runtime would only be able to match the call to the Unit overload, never to Weight or Measure overloads, so it would keep calling Add(Unit) over and over until... boom. IMO this is worse than type-checking; at least if that barfs you know what happened. SOEs are a pain.
KeithS
A `StackOverflowException` will NOT happen in this case. There is NO recursive call. The calls will be resolved to the proper derived classes' methods. `this` inside a derived class refers to the derived class itself, not to its base class. This pattern is used to implement [double-dispatch](http://en.wikipedia.org/wiki/Double_dispatch).
Jordão
A: 

I actually got the idea from GenericTypeTea, but it occurred to me to try a different method.

public interface IUnit
{
    T Add<T>(T unit) where T : IUnit<T>;
}

public interface IUnit<T>
{
    T Add(T unit);
}

    abstract class Unit : IUnit
    {
    }

    class Measure : Unit, IUnit<Measure>
    {
    public Measure Add(Measure unit)
    {
        throw new NotImplementedException();
    } 
    }

    class Weight : Unit, IUnit<Weight>
    {
    public Weight Add(Weight unit)
    {
        throw new NotImplementedException();
    }
    }
Stacey
A: 

If you absolutely had to be using two lists of the base class:

public abstract class Unit()
{
    public abstract Unit Add(Unit other);

    public void MatchType(Unit other)
    {
        if(this.GetType() != other.GetType()) 
            throw new ArgumentException("Units not of same type");
    }
}

...then implement the following in each derived class:

public override void Add(Unit other)
{
   MatchType(other);

   //actual implementation
}

then extend this functionality in your derived classes with the actual Add implementation.

Honestly, I do not see the point of what you're trying to do. It wouldn't work unless both lists contained only Measures or only Weights, and you're just hiding that fact from the consumer by putting type-checking into Unit. That's just asking for trouble.

I would define the code you have these two lists in as follows:

public List<T> AddLists<T>(List<T> firstList, List<T> secondList) where T:Unit
{
   //your code to add elements
}

... then use a generic Unit.Add() method as follows:

public static void Add<T>(this T item, T other) where T:Unit
{
   //perform something that works like item += other
}

You'd have to use lists strongly typed to Weight or Measure. You can try to convert a List's generic parameter using Linq:

listOfMeasures = listOfUnits.OfType<Weight>().ToList();

Of course this will cause Weights to be filtered out of the list, but this can work to your advantage:

listOfWeights = listOfUnits.OfType<Weight>().ToList();
listOfMeasures = listOfUnits.OfType<Measure>().ToList();

var addedListOfMeasures = AddLists(listOfMeasures, otherListOfMeasures);
var addedListOfWeights = AddLists(listofWeights, otherListOfWeights);

The above code, used with the generic AddLists and Unit.Add() methods I laid out, will be type-safe and thus won't throw any runtime exceptions. Doesn't make it good; any new Unit you create will require adding more code to this to handle each new type.

KeithS