2

I have a set of methods each of which return an ObservableCollection for various types of T, and would like to be able to write a factory method that returns these methods, based on an enum value sent in. For example, suppose I have the following highly realistic methods...

public ObservableCollection<Gribble> GetGribbles() {
  return new ObservableCollection<Gribble>();
}

public ObservableCollection<Gribulator> GenerateGribulators() {
  return new ObservableCollection<Gribulator>();
}

public ObservableCollection<Knepple> MakeKnepples() {
  return new ObservableCollection<Knepple>();
}

...and the following enum...

public enum ListNames {
  Gribbles,
  Gribulators,
  Knepple
}

Then I would like to be able to do this...

Func<ObservableCollection<Gribble>> someGribbles = 
       MakeFunc<Gribble>(ListNames.Gribbles);

The reason for this is that I have a helper class with a signature like this...

public void GetList<T>(ListNames listName, 
  Func<ObservableCollection<T>> serviceCall, 
  Action<ObservableCollection<T>> handleList)

serviceCall is the method that gets the list, and handleList is a callback method. At the moment, wherever this method is called, the code has to pass the actual function that gets the list.

However, this is proving to be a bad design choice, as it means that if two parts of the code both make a call to GetList for a particular list, they could pass in two different service calls, which will cause inconsistent results. I want to encapsulate the service call in the MakeFunc method, which would be called from GetList, meaning that the code that uses GetList never sees the actual service call, and so can't do anything wrong.

I have tried a few things, but am struggling to get anything that will compile. The closest I have got is the following...

public Func<ObservableCollection<T>> MakeFunc<T>(ListNames listName) {
  switch (listName) {
    case ListNames.Gribbles:
      return GetGribbles;
    case ListNames.Gribulators:
      return GenerateGribulators;
    case ListNames.Knepple:
      return MakeKnepples;
    default:
      throw new ArgumentException("Unknown list name");
  }
}

...but this gives a compiler errors along the lines of "GetGribbles() has the wrong return type" and the tooltip that pops up when I hover my mouse over GetGribbles in the MakeFunc method says

Expected a method with 'ObservableCollection<T> GetGribbles()' signature

Is it possible to do what I want? If so, anyone able to explain what I did wrong?

I'm using C# 4.0 in Visual Studio 2010 if it makes any difference.

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
Avrohom Yisroel
  • 8,555
  • 8
  • 50
  • 106
  • hmm... is 'Gribble' different from 'Gribbles'? if so, what is the type of 'Gribble'? – OJ Raqueño Jun 20 '13 at 23:51
  • Does `Gribble`,`Gribulator` and `Knepple` have a common base class?, if not you are going to have to cast the collections, as T will not resove correctly as all of the Lists have different return types – sa_ddam213 Jun 20 '13 at 23:55
  • You are not using the generic type parameter T in your MakeFunc method to return the appropriate method, but are instead trying to return the defined Get-methods directly - but the code will not compile because their method signatures are different. Also, I think you should explain what problem you are trying to solve, as it would allow people to suggest solutions you perhaps haven't thought of. – Morten Mertner Jun 20 '13 at 23:58
  • @Alexei Levenkov, why did you remove my last line? Does StackOverflow have some policy against being polite? I'm not trying to be rude here, but I don't see any benefit in removing a line that thanked people in advance for any help they may offer. Your other edits seemed perfectly sensible, I just don't understand the necessity for this one. – Avrohom Yisroel Jun 21 '13 at 10:34
  • @ojr Gribble is one of the classes, and Gribbles is an element of the ListNames enum. It's plural because the list is a collection of Gribble objects. – Avrohom Yisroel Jun 21 '13 at 10:35
  • @Morten Mertner, I guessed that this was part of the problem, but couldn't see how to get around it. The GetGribbles, etc methods are not generic, each one returns a known type, and I couldn't see how to tell the compiler what they were. I was hoping someone would be able to point out a way of getting around this. – Avrohom Yisroel Jun 21 '13 at 10:37
  • @sa_ddam213 please see my reply to p.s.w.g below. I tried implementing a common interface, but got exactly the same errors. Thanks – Avrohom Yisroel Jun 21 '13 at 10:38
  • On thankyounotes - [Should 'Hi', 'thanks,' taglines, and salutations be removed from posts?](http://meta.stackexchange.com/questions/2950/should-hi-thanks-taglines-and-salutations-be-removed-from-posts) - be polite by not wasting peoples' time/post space on thanks, instead participate actively (thanks for that). – Alexei Levenkov Jun 21 '13 at 19:13
  • @AlexeiLevenkov thanks for the link, but I'm with the opinion that short things like "Thanks" should be left in. We're human, we're not machines. Still, you've obviously made your decision, not worth me trying to fight it! – Avrohom Yisroel Jun 23 '13 at 15:43

1 Answers1

4

Well if the Gribble, Gribulator, and Knepple classes share a common base type or implement a common interface, you can just do this:

public Func<ObservableCollection<ICommonInterface>> MakeFunc(ListNames listName) 
{
   ...
}

If they don't have a common base type or interface, you'd have to do this:

public Func<ICollection> MakeFunc(ListNames listName) 
{
   ...
}

Which, admittedly, is not a very good solution. Note: you can replace ICollection with INotifyCollectionChanged or whatever non-generic interface that you need most.

In both cases, you have to rely on the caller to specify the data type (even if the caller specifies the enum value as well). So, you could also get rid of the enum, and go for something like this:

public Func<ObservableCollection<T>> MakeFunc<T>() 
{
  if (typeof(T) == typeof(Gribble))
      return () => GetGribbles() as ObservableCollection<T>;
  if (typeof(T) == typeof(Gribulator))
      return () => GenerateGribulators() as ObservableCollection<T>;
  if (typeof(T) == typeof(Knepple))
      return () => MakeKnepples() as ObservableCollection<T>;
  throw new ArgumentException("Unknown list name");
}

Then the caller could simply write:

var gribblesFunc = MakeFunc<Gribble>();
p.s.w.g
  • 146,324
  • 30
  • 291
  • 331
  • Thanks for the suggestion, but it still doesn't compile. I copied your code exactly, and got the same compiler error. If I hover my mouse over the error, the pop up now says "Cannot cast expression of type 'method group' to type 'Func>'. Any ideas? Thanks again – Avrohom Yisroel Jun 21 '13 at 10:26
  • Forgot to mention that I also tried having them all implement a common interface, but got the same compiler errors as at first. Thanks again. – Avrohom Yisroel Jun 21 '13 at 10:32
  • thanks for the update but now I get a compiler error of "Cannot convert type 'System.Collections.ObjectModel.ObservableCollection' to 'System.Collections.ObjectModel.ObservableCollection'" Did I miss something? I copied and pasted your code. Thanks again. – Avrohom Yisroel Jun 21 '13 at 14:30
  • @AvrohomYisroel Nope that was my fault again. I would've thought casting would work, but it turns out you need to use `as`. I've tested this, seems to work correctly. – p.s.w.g Jun 21 '13 at 14:42