This is difficult to use for support because of how the C # compiler performs overload resolution and how it decides which method to bind to.
The first problem is that restrictions are not part of the method signature and will not be considered to allow overloading.
The second problem you have to overcome is that the compiler chooses the best match for the available signatures - which when working with generics usually means SomeMethod<T>(T) will be considered a better match than SomeMethod<T>( IEnumerable<T> ) . especially if you have parameters like T[] or List<T> .
But more fundamentally, you should think about whether working on a single value in comparison with a set of values is really the same operation. If they are logically different, then you probably want to use different names just for clarity. Perhaps there are some use cases in which it can be argued that the semantic differences between individual objects and collections of objects do not make sense ... but in this case, why use two different methods in general? It is not clear that method overloading is the best way to express differences. Let's look at an example that gives confusion:
Cache.GetOrAdd("abc", () => context.Customers.Frobble() );
First, note that in the above example, we prefer to ignore the returned parameter. Secondly, note that we call some Frobble() method in the Customers collection. Now can you tell me which overload of GetOrAdd() will be called? Obviously, without knowing the type returned by Frobble() , this is not possible. Personally, I believe that code whose semantics cannot be easily understood from the syntax should be avoided whenever possible. If we choose the best names, this problem will be fixed:
Cache.Add( "abc", () => context.Customers.Frobble() ); Cache.AddRange( "xyz", () => context.Customers.Frobble() );
Ultimately, there are only three options for eliminating the ambiguous methods in your example:
- Change the name of one of the methods.
- Pass an
IEnumerable<T> wherever you call the second overload. - Change the signature of one of the methods so that the compiler can distinguish.
Option 1 is self-evident, so I will no longer talk about it.
Features 2 are also easy to understand:
var customers = Cache.GetOrAdd("All", () => (IEnumerable<Customer>)context.Customers.ToArray());
Option 3 is harder. Let's see how we can achieve this.
The approach uses a Func<> delegate signature change, for example:
T GetOrAdd<T> (string cachekey, Func<object,T> fnGetItem) T[] GetOrAdd<T> (string cachekey, Func<IEnumerable<T>> fnGetItem) // now we can do: var customer = Cache.GetOrAdd("First", _ => context.Customers.First()); var customers = Cache.GetOrAdd("All", () => context.Customers.ToArray());
Personally, I find this option terribly ugly, unintuitive, and confusing. Introducing an unused parameter is horrible ... but unfortunately it will work.
An alternative way to change the signature (which is somewhat less scary) is to make the return value a out :
void GetOrAdd<T> (string cachekey, Func<object,T> fnGetItem, out T); void GetOrAdd<T> (string cachekey, Func<IEnumerable<T>> fnGetItem, out T[]) // now we can write: Customer customer; Cache.GetOrAdd("First", _ => context.Customers.First(), out customer); Customer[] customers; var customers = Cache.GetOrAdd("All", () => context.Customers.ToArray(), out customers);
But is it really better? This prevents us from using these methods as parameters of other method calls. It also makes the code less comprehensible and less comprehensible, IMO.
The last alternative that I will introduce is to add another common parameter to methods that identify the type of return value:
T GetOrAdd<T> (string cachekey, Func<T> fnGetItem); R[] GetOrAdd<T,R> (string cachekey, Func<IEnumerable<T>> fnGetItem); // now we can do: var customer = Cache.GetOrAdd("First", _ => context.Customers.First()); var customers = Cache.GetOrAdd<Customer,Customer>("All", () => context.Customers.ToArray());
So you can use the hints to help the compiler choose the overload for us ... of course. But look at all the extra work that we have to do as a developer to get there (not to mention the introduction of ugliness and the possibility of mistakes). Is it really worth the effort? In particular, when is there a simple and reliable technique (naming methods differently) to help us?