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).
Best Answer
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:This is how I would wrap it:
The producer:
The consumer:
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.