5

So I'm using a C# framework that has a great example of where generics would be useful, except they weren't used. For simplicity's sake, we will say it was a list (I know C# has its own list), but instead of being List where I can specify the T, they made it List, resulting in nightmarish type checking throughout the code.

I want to fix this, but I can't edit the framework.

So I see three ways to fix this.

Extend, wrap, or both.

So the basic question is, which is the best way to add generics?

Is there a design pattern particulary for this?

Now, while I'm not sure wrapping or extending is better, if this was a simple case I would have gone with one of them and not even considered doing both.

But, the CustomList is used everywhere, and I only want to replace it bit by bit.

Extending allows me to use the TypedCustomList as a CustomList, so that if I change where CustomList are being created to instead by TypedCustomList, later in code's execution the code can still treat it like a CustomList.

But, I can't treat a CustomList object as a TypedCustomList (even if I ensure the generic type is correct for that given CustomList). (Or am I missing how to do this, which would solve the problem?)

If a wrap it, I can now work with existing CustomLists, but I have to expose the internal CustomList object to pass it on to other code (for example, if I call a method expecting CustomList, I have to do instanceOfTypedCustomList._customList, I cannot pass instanceOfTypedCustomList).

If I wrap and extend it, I get take an existing CustomList and treat it like a TypedCustomList while still being able to treat my TypedCustomList as a CustomList. But, as you can see below, the 'Both' option looks the worst.

Extending

public class TypedCustomList<T> : CustomList
{
  public TypedCustomList()
    :base()
  {
  }

  public T getElementAt(int i)
  {
    return (T) base.getElementAt(i);
  }

  public void addElement(T element)
  {
    base.addElement(element);
  }

  ...ect.
}

Wrapping:

public class TypedCustomList<T>
{
  CustomList _customList { get; private set;}
  public TypedCustomList(CustomList cl)
  {
    _customList = cl;
  }

  public T getElementAt(int i)
  {
    return (T) _customList.getElementAt(i);
  }

  public void addElement(T element)
  {
    _customList.addElement(element);
  }

  ...ect.
}

Both:

public class TypedCustomList<T> : CustomList
{
  private CustomList _customList;

  public TypedCustomList()
    :base()
  {
    _customList = null;
  }

  public TypedCustomList(CustomList cl)
  {
    _customList = cl;
  }

  public T getElementAt(int i)
  {
    if(_customList == null)
    {
      return (T) base.getElementAt(i);
    }
    else
    {
      return (T) _customList.getElementAt(i);
    }
  }

  public void addElement(T element)
  {
    if(_customList == null)
    {
      base.addElement(element);
    }
    else
    {
      _customList.addElement(element);
    }
  }

  ...ect.
}

P.S.

More background which may be relevant to the problem.

There is one class that has a CustomList that is used all over the application, with different parts of the application with each instance having different object types (over a dozen in total). I want to eventually have this class have a unique TypedCustomList for every use. But until I can be sure that all references to it's CustomList are gone, I wanted to add some error handling. So I was adding something like the following:

public ClassThatUsesCustomList
{
...
private CusotmList _list;
private TypedCustomList<SomeObject> _SomeObjectCustomList;

public CustomList list
{
    get
    {
      //Already existing code
      if(_list == null)
      {
        _list = new CustomList();
      }

      //My code
      catchMissedGetSet(_list);


      return _list;
    }
    set
    {
      catchMissedGetSet(value);
      _list = value;
    }
}

public SomeObjectCustomList
{
  get
  {
    if(_SomeObjectCustomList == null)
    {
      _SomeObjectCustomList = new TypedCustomList<SomeObject>();
    }

    return _SomeObjectCustomList;
  }
  set
  {
    _SomeObjectCustomList = value;
  }
}


private void catchMissedGetSet(CustomList cl)
{
  bool findMissedSpots = true;

  if(cl == null || cl.Count == 0 || cl[0] is SomeObject)
  {
    SomeObjectCustomList = new SomeObjectCustomList(cl);  //Using wrapping.
    if(findMissedSpots && System.Diagnostics.Debugger.IsAttached)
    {
      System.Diagnostics.Debugger.Break();  
      //Hey, you missed a place.  Use step out to find it.
    }
  }
}


...
}

I did this to catch any spots I missed and to avoid any bugs. It requires me wrapping it (as far as I can tell).

12
  • 1
    Sounds like you're having an X/Y problem: You're asking for a solution in the form of X but actually need a solution in the form of Y. The solution to X/Y problems is to describe your actual problem and people will then be able to show you the solution which is Y even though you're not aware Y exists. Maybe what you're asking for here is the correct solution, but I think you should give an apt definition of your problem first otherwise you'll get a crappy solution. (It doesn't sound like you have a problem to me - why do you care how a library's type is implemented?) Commented Dec 3, 2014 at 20:56
  • @JimmyHoffa: probably because of the "nightmarish type checking throughout the code". Commented Dec 3, 2014 at 20:58
  • @MikePartridge yeah I get that, but it's not well described how that is a problem. I suspect that may be solvable in a different fashion than described here Commented Dec 3, 2014 at 21:05
  • Simply implementing a wrapper around an existing non-generic library is unlikely to provide any benefit, other than a more consistent syntax. The primary reason to have generics is to avoid boxing, and you won't achieve that objective this way. Commented Dec 3, 2014 at 21:18
  • Describing the actual problem would be far too specific, would be filled with numerous more head scratching design decisions that may distract from the core question, and I don't want to post any actual source code from the problem less I anger the security team or the lawyers. As to why I care, it is because we are using that type. Imagine if instead of List<T>, Microsoft only gave you List<object>. Yes, you could be 'chin up' and just use it, but why forego generics and lose strong typing? Commented Dec 3, 2014 at 21:27

1 Answer 1

1

The answer is wrapping, but it is considerably more work than implied here. I see why you are trying to do this though, because after a certain size of the code base it indeed can be a nightmare to work with typecasting every time you call a library function. Subclassing CustomList is not advisable, as it would expose all the functions that return or expect an object. Having a public property as in the second example is, for the same reason, also unadvisable. I believe you are facing a situation similar to the following:

interface Producer
{
  CustomList produce();
}

interface Consumer
{
  void consume(CustomList list);
}

This is how I would wrap it:

public class TypedCustomList<T>
{
  private readonly CustomList _customList;
  internal CustomList customList
  {
    get { return _customList; }
  }

  internal TypedCustomList(CustomList cl)
  {
    _customList = cl;
  }

  public T getElementAt(int i)
  {
    return (T) _customList.getElementAt(i);
  }

  public void addElement(T element)
  {
    _customList.addElement(element);
  }

  ... ect.
}

The producer:

class TypedProducer<T>
{
  private readonly Producer producer = new Producer();
  public TypedCustomList<T> produce()
  {
    return new TypedCustomList<T>(producer.produce())
  }
}

The consumer:

class TypedConsumer<T>
{
  private readonly Consumer consumer = new Consumer();
  public void consume(TypedCustomList<T> list)
  {
    consumer.consume(list.customList);
  }
}

You will need to put the wrapping classes to their own assembly, so that they hide the internal parts.

I can safely suggest you this solution, which I beleive generally considered a good practice. You will need to wrap the subset of the library you are using in this manner.

Not the answer you're looking for? Browse other questions tagged or ask your own question.