Tom
Tom

Reputation: 2254

Is it possible to create a method that returns one of two possible types?

I have 2 data structures: Dictionary<string, string> and Multimap<string, string>. Multimap is really just a Dictionary under the hood. I took must of the code from this question. Here's the class definition:

public class Multimap<TKey, TValue> : Dictionary<TKey, HashSet<TValue>>
{ ... }

Both data structures have a .Add(TKey key, TValue value) method.

I have a class that is responsible for populating these maps from certain files. I currently have the following two methods:

    public Dictionary<string, string> PopulateDictionary(...)
    {
        Dictionary<string, string> returnDictionary = new Dictionary<string, string>();
        ...
        foreach (...)
        {
            ...
            returnDictionary.Add(key, value);
        }
        return returnDictionary;
    }

    public Multimap<string, string> PopulateMultimap(...)
    {
        Multimap<string, string> returnMultimap = new Multimap<string, string>();
        ...
        foreach (...)
        {
            ...
            returnMultimap.Add(key, value);
        }
        return returnMultimap;
    }

As you can see, they're exactly the same, both around 25 lines long, and the only difference is their return type. What I am looking to do is condense this into one method. My first attempt was to have the method

public Dictionary<string, object> PopulateGenericDictionary(...)
{ ... }

Where object was either string or HashSet<string>. But I didn't have much luck casting from Dictionary<string, object> to Multimap<string, string>.

Extracting the logic out of the methods is an option, but it's not great. Because of the foreach loops, there's always going to be some logic inside the two methods. You do end up with methods that are twice as small, but there's still two identical methods, which doesn't truly solve the problem.

This would be my ideal method structure:

public Dictionary<string, string> PopulateDictionary(...)
{
    return MethodThatDoesAllTheLogic(...);
}
public Multimap<string, string> PopulateMultimap(...)
{
    return MethodThatDoesAllTheLogic(...);
}
public ??? MethodThatDoesAllTheLogic(...)
{ ... }

I've been fiddling around with casting and generics, but I just can't get it to work. Any ideas?

Edit

I have used millimoose's solution. Here's my code now:

    public Dictionary<string, string> GenerateDictionary(...)
    {
        Dictionary<string, string> returnMap = new Dictionary<string, string>();
        PopulateDictionary(returnMap.Add, ...);
        return returnMap;
    }

    public Multimap<string, string> GenerateMultimap(...)
    {
        Multimap<string, string> returnMap = new Multimap<string, string>();
        PopulateDictionary(returnMap.Add, ...);
        return returnMap;
    }

    private static void PopulateGenericDictionary(Action<string, string> addFunc, ...)
    {
        ...
        foreach (...)
        {
            addFunc(key, value);
        }
    }

Much cleaner!

Upvotes: 4

Views: 200

Answers (6)

Piotr Trzpil
Piotr Trzpil

Reputation: 282

See if this approach will be useful: The key is to make abstraction on creation of the object (Dictionary or Multimap) and aquiring the values - the two differences in the populating method.

public  Dictionary<string, TValue> Populate<TValue>( Dictionary<string, TValue> returnDict, Func<SomeType, TValue> valueProvider)
{
    string key = null;
    ...
    foreach (...)
    {
        ...
        returnDict.Add(key, valueProvider(value));
    }
    return returnDict;
}

The example invocation is can be:

public void Test()
{
    Populate(new Multimap<string, HashSet<string>>(), (t) => new HashSet<HashSet<string>>());
}

I'm not sure if the valueProvider delegate will be suited to your problem. Try to give more information about it.

Upvotes: 0

millimoose
millimoose

Reputation: 39950

To work around the lack of a common interface, you can invent one ad-hoc using a bunch of delegate type parameters:

void MethodThatDoesAllTheLogic(Action<string, string> addFunc)
{
    // ...
    addFunc(key, value);
    // ...
}

public Dictionary<...> PopulateDictionary()
{
    // ...
    MethodThatDoesAllTheLogic(result.Add);
}

(Adding more parameters as necessary.)

Upvotes: 8

Servy
Servy

Reputation: 203820

I would avoid having the helper method create the actual collection at all; have it just populate an existing collection. That can be done much more effectively, since the Add method has the same signature in both cases. We can just use a delegate to accept the Add method:

public static void PopulateMapping<TKey, TValue>(Action<TKey, TValue> addMethod,
    IEnumerable<TKey> data) //include other parameters needed to populate the data
{
    foreach (var key in data)
    {
        addMethod(key, default(TValue));
    }
}

Then it would be used like this:

public static Dictionary<string, string> PopulateDictionary()
{
    Dictionary<string, string> output = new Dictionary<string, string>();
    PopulateMapping<string, string>(output.Add, new string[] { "a" });
    return output;
}

Upvotes: 3

Eluvatar
Eluvatar

Reputation: 2295

in C# with generics you can require them to extend or implement a specific class in our case Dictionary, the following is how you might achieve that.

public T Populate<T>(string val) where T : Dictionary<string, string>, new()
        {
            T returnDict = new T();
            returnDict.Add("key", "val");
            return returnDict;
        }

Upvotes: -1

Justin Pihony
Justin Pihony

Reputation: 67075

If you are only looking for an Add method, then both objects should share IDictionary. However, that Add method only uses objects. That is probably the closest that you can get without having to use generics in the method...but again you lose the benefits of generics at that point.

Upvotes: 0

Ann L.
Ann L.

Reputation: 13965

If your inner logic is truly identical except for what type TValue is - and I mean word-for-word identical - then you could do something like:

IDictionary<string, TValue> MethodThatDoesAllTheLogic<TValue>(whatever)
{
  // word for word-identical logic
}

I made the method take TValue as its only type parameter because that's the only difference (in the example you showed): both methods have string as the first type parameter.

ETA: This assumes that MultiMap implements IDictionary<K,V>. Since you said that it inherited from Dictionary<K,V> I assumed that it did.

Upvotes: -1

Related Questions