Search code examples
c#genericscollectionsoverloadinggeneric-collections

Looking for non-type-specific method of handling Generic Collections in c#


My situation is this. I need to run some validation and massage type code on multiple different types of objects, but for cleanliness (and code reuse), I'd like to make all the calls to this validation look basically the same regardless of object. I am attempting to solve this through overloading, which works fine until I get to Generic Collection objects.

The following example should clarify what I'm talking about here:

private string DoStuff(string tmp) { ... }

private ObjectA DoStuff(ObjectA tmp) { ... }

private ObjectB DoStuff(ObjectB tmp) { ... }

...

private Collection<ObjectA> DoStuff(Collection<ObjectA> tmp) {
    foreach (ObjectA obj in tmp) if (DoStuff(obj) == null) tmp.Remove(obj);
    if (tmp.Count == 0) return null;
    return tmp;
}

private Collection<Object> DoStuff(Collection<ObjectB> tmp) {
    foreach (ObjectB obj in tmp) if (DoStuff(obj) == null) tmp.Remove(obj);
    if (tmp.Count == 0) return null;
    return tmp;
}

...

This seems like a real waste, as I have to duplicate the exact same code for every different Collection<T> type. I would like to make a single instance of DoStuff that handles any Collection<T>, rather than make a separate one for each.

I have tried using ICollection, but this has two problems: first, ICollection does not expose the .Remove method, and I can't write the foreach loop because I don't know the type of the objects in the list. Using something more generic, like object, does not work because I don't have a method DoStuff that accepts an object - I need it to call the appropriate one for the actual object. Writing a DoStuff method which takes an object and does some kind of huge list of if statements to pick the right method and cast appropriately kind of defeats the whole idea of getting rid of redundant code - I might as well just copy and paste all those Collection<T> methods.

I have tried using a generic DoStuff<T> method, but this has the same problem in the foreach loop. Because I don't know the object type at design time, the compiler won't let me call DoStuff(obj).

Technically, the compiler should be able to tell which call needs to be made at compile time, since these are all private methods, and the specific types of the objects being passed in the calls are all known at the point the method is being called. That knowledge just doesn't seem to bubble up to the later methods being called by this method.

I really don't want to use reflection here, as that makes the code even more complicated than just copying and pasting all the Collection<T> methods, and it creates a performance slowdown. Any ideas?

---EDIT 1--- I realized that my generic method references were not displaying correctly, because I had not used the html codes for the angle brackets. This should be fixed now.

---EDIT 2--- Based on a response below, I have altered my Collection<T> method to look like this:

private Collection<T> DoStuff<T>(Collection<T> tmp) {
    for (int i = tmp.Count - 1; i >= 0; i--) if (DoStuff(tmp[i]) == null) tmp.RemoveAt(i);
    if (tmp.Count == 0) return null;
    return tmp;
}

This still does not work, however, as the compiler cannot figure out which overloaded method to call when I call DoStuff(tmp[i]).


Solution

  • You need to pass the method you want to call into the generic method as a parameter. That way the overload resolution happens at a point where the compiler knows what types to expect.

    Alternatively, you need to make the per-item DoStuff method generic (or object) to support any possible item in the collection.

    (I also separated the RemoveItem call from the first loop, so that it isn't trying to remove an item from the same list being iterated.)

    private Collection<T> DoStuff<T>(Collection<T> tmp, Func<T, T> stuffDoer)
    {
        var removeList = tmp
            .Select(v => stuffDoer(v))
            .Where(v => v == null)
            .ToList();
    
        foreach (var removeItem in removeList) tmp.Remove(removeItem);
    
        if (tmp.Count == 0) return null;
        return tmp;
    }
    
    private class ObjectA { }
    private class ObjectB { }
    
    private string DoStuff(string tmp) { return tmp; }
    
    private ObjectA DoStuff(ObjectA tmp) { return tmp; }
    
    private ObjectB DoStuff(ObjectB tmp) { return tmp; }
    

    Call using this code:

    var x = new Collection<ObjectA>
    {
        new ObjectA(),
        new ObjectA(),
        null
    };
    
    var result = DoStuff(x, DoStuff);