views:

86

answers:

2

We have inherited a project that is a wrapper around a section of the core business model.

There is one method that takes a generic, finds items matching that type from a member and then returns a list of that type.

public List<T> GetFoos<T>()
{
    List<IFoo> matches = Foos.FindAll(
        f => f.GetType() == typeof(T)
    );

    List<T> resultList = new List<T>();
    foreach (var match in matches)
    {
        resultList.Add((T)obj);
    }
}

Foos can hold the same object cast into various classes in inheritance hierarchy to aggregate totals differently for different UI presentations. There are 20+ different types of descendants that can be returned by GetFoos.

The existing code basically has a big switch statement copied and pasted throughout the code. The code in each section calls GetFoos with its corresponding type.

We are currently refactoring that into one consolidated area, but as we are doing that we are looking at other ways to work with this method.

One thought was to use reflection to pass in the type, and that worked great until we realized the Invoke returned an object, and that it needed to be cast somehow to the List <T>.

Another was to just use the switch statement until 4.0 and then use the dynamic language options.

We welcome any alternate thoughts on how we can work with this method. I have left the code pretty brief, but if you'd like to know any additional details please just ask.

Update

The switch statement was originally using strings and the first pass was moving it into a Presenter with something like this: (No refactoring done on the switch, just where the data went).

// called on an event handler in FooPresenter
// view is the interface for the ASPX page injected into FooPresenter's constructor
// wrapper is the instance of the model wrapper that has the GetFoos method
// selectedFooName is the value of a DropDownList in the Page
//  letting the user say how they want to see the animals
//   its either one big group (Animal)
//   or individual types (Tiger, Lion)
private void LoadFoos(string selectedFooName)
{
    switch (selectedFooName)
    {
        case "Animal":  // abstract base class for all other types
            this.view.SetData(this.wrapper.GetFoos<Animal>(); 

        case "Lion":
            this.view.SetData(this.wrapper.GetFoos<Lion>();
            break;

        case "Tiger":
            this.view.SetData(this.wrapper.GetFoos<Tiger>();    
            break;

        case "Bear":
            this.view.SetData(this.wrapper.GetFoos<Bear>();
            break;    
    }
}

The View implementation (codebehind for an ASPX page)

public void SetData<T>(List<T> data)
{
    // there is a multiview on the page that contains user controls with 
    // grid layouts for the different types

    // there is a control for the common type of "Animal"
    // and other controls for Tiger, Bear, etc

    // the controls contain a 3rd party grid of pain 
    // and the grids' binding event handlers cast the data item 
    // for the row and do some stuff with it specific to that type
}

Our first pass was going to be at least using the Type in the switch statement, or adding an enum.

I played around with using the Strategy Pattern but had to stop when I got to the loading factory returning the List again and realizing I didn't have the type.

+2  A: 

It's difficult without seeing the code calling GetFoos()... If you can show more code describing how this is being called, we can suggest how to refactor that.

It sounds like the solution is to make your calling routine a generic routine as well - so that it can avoid the "switch" around the 20 types by just using a single generic type, specified as needed. However, this may not be feasable, but again, without code, it's difficult to know...

That being said, you can refactor GetFoos to be much simpler:

public List<T> GetFoos<T>()
{
    return Foos.OfType<T>().ToList();
}

Edit: As Eric Lippert points out, the above code returns any type that is a type of T, but also subclasses of T. Although this is most likely the behavior that would actually be desired, it is different than the original code. If this is not desirable for some reason, you could, instead, use:

public List<T> GetFoos<T>()
{
    Type type = typeof(T);
    return Foos.Where(item => item.GetType() == type).ToList();
}

This will have the same behavior as the original code.

Reed Copsey
You said the same thing. +1
Richard Hein
Note that your refactoring does not do the same thing. Suppose Foos contains Lions, Tigers and Bears. Their version, GetFoos<Feline> returns an empty list because none of those are of *exactly* the type Feline. Yours does the (probably more sensible) operation of returning a list of the Lions and Tigers. My point is that a refactoring that introduces a semantic change is not actually a *refactoring*. In this case it's probably a bug fix.
Eric Lippert
@Eric: Thanks for the good point. I added a section to mention that, as well. (I believe my first run is probably what was intended, but you are absolutely correct in that it's not a refactoring, as it changes the behavior of the existing code...)
Reed Copsey
Thanks for the response. I have updated the question with more details.
blu
@Eric: Ironically, if you look at the update - my original is actually what was desired, and the OP's code was buggy ;)
Reed Copsey
@Reed Can you talk about what is buggy in the code? I don't particularly care for how it works, but it does accomplish what the original developer wanted, and has been working in production for some time. The whole underlying object model is really awkward and this needs to be redone at some point, so I welcome any thoughts you have. Also, any thoughts on the switch statement would be great. Thanks.
blu
@blu: The original code, as you posted, will not return "Bear" instances if you pass in "Animal" as the generic type. From your description, using GetFoos<Animal> should return all animals, but your original code will not return subclasses of Animal (Bear, Tiger, etc). The OfType<T> option I pasted originally does return this.
Reed Copsey
@Reed I totally agree, and its a "feature" of the current implementation that irks us right now. Say we have 3 object types an instance can be based on its ancestors. Bear would be a Bear class, a NonCat class, and an Animal class. Well the current implementation stores 3 copies of the Bear in the same list, one for each of those types. So I totally agree on what you said and I wish it didn't work like this, but the code does what it is supposed to do. Just another thing to be changed coming up, but the first step is the Switch statement.
blu
@Eric You are spot on in your comment, and that is exactly why its implemented the way it is. If interested read the previous comment of mine as to why it works this way.
blu
A: 

Something like this?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace SO_2480770 {
    interface IFoo {}
    class MyBase : IFoo {}
    class Bar : MyBase {}

    class Program {               
        IEnumerable<IFoo> Foos { get; set; }

        static void Main(string[] args) {           
            List<MyBase> bases = new List<MyBase>() { new MyBase(), new MyBase() };
            List<Bar> bars = new List<Bar>() { new Bar(), new Bar() };            
            Program p = new Program();
            p.Foos = bases.Concat(bars);            
            var barsFromFoos = p.GetFoos<Bar>();
            var basesFromFoos = p.GetFoos<MyBase>();
            Debug.Assert(barsFromFoos.SequenceEqual(bars));
            Debug.Assert(basesFromFoos.SequenceEqual(bases.Concat(bars)));
            Debug.Assert(!barsFromFoos.SequenceEqual(bases));
            Console.ReadLine();
        }               

        public List<T> GetFoos<T>() where T : IFoo {            
            return Foos.OfType<T>().ToList();
        }
    }    
}

To get rid of big switch statements, you either have to push the generics futher up. I.E. make the method that has the switch statement take a generic type parameter, and keep going until you can't go any futher, if you have to, up the calling chain. When that gets too difficult, think about design patterns such as abstract factory, factory, template methods etc.... It depends on how complex the calling code is.

Richard Hein