简体   繁体   中英

Convert dictionary, with string key, to dictionary with enum key using Generics

I have a bunch of dictionaries (with string as key) that I read from an external source. I would like to convert all the dictionaries to dictionaries with different enums as keys. For each enum, I have a custom method that converts the string to an enum.

Rather than writing one convert-function for each enum type, I was hoping I could write one generic method for all.

I'm not really that familiar with generics (or type-checking for that matter), but the below code is an attempt at writing the function. The vs-intellisense does not like this solution. Is it possible to do what I'm trying to do? And if so, how?

I use.Net Framework 4.5.2 so I guess that is C# 5.0 (and updating is currently not an option).

public enum Product { Product1, Product2, Product3 };
public enum Fund { Fund1, Fund2, Fund3 }
//...and lots of different enums

private static Dictionary<T, double> ConvertDict<T>(Dictionary<string, double> dict) where T : Enum
{
    var returnDict = new Dictionary<T, double>();
    //Here ideally I would do something like this
    switch (typeof(T))
    {
        case typeof(Product): //This a no go
            foreach (var keyValuePar in dict)
                returnDict.Add(CustomProductEnumConverter(keyValuePar.Key), keyValuePar.Value);
            break;
        case typeof(Fund): //This a no go
            foreach (var keyValuePar in dict)
                returnDict.Add(CustomFundEnumConverter(keyValuePar.Key), keyValuePar.Value);
            break;
        default:
            throw new Exception("Unknown enum-type");        
    }
    return returnDict;
}

public static Product CustomProductEnumConverter(string productName)
{
    //No clear link between enum name and string value...
    if (productName == "base012")
        return Product.Coffee;
    if (productName == "defa341")
        return Product.Milk;
    if (productName == "urak451")
         return Product.Juice;
    //...
}

The idea is that I would be able to call my new function like this

var prodDictA = ConvertToDict<Product>(rawProdDictA)
var prodDictB = ConvertToDict<Product>(rawProdDictB)
var fundDictA = ConvertToDict<Fund>(rawFundDictA)
//etc...

You can solve it by injecting the converter for the key; from string to the wanted key type T .

Here's an example:

public enum Product { Product1, Product2, Product3 };
public enum Fund { Fund1, Fund2, Fund3 }
//...and lots of different enums

private static Dictionary<T, double> ConvertDict<T>(
    Dictionary<string, double> dict,
    Func<string, T> convertKey)
{
    var returnDict = new Dictionary<T, double>();

    foreach (var kvp in dict)
    {
        returnDict.Add(convertKey(kvp.Key), kvp.Value);
    }

    return returnDict;
}

private static Product ConvertProductName(string productName)
{
    if (productName == "prod1")
        return Product.Product1;
    if (productName == "prod2")
        return Product.Product2;
    if (productName == "prod3")
        return Product.Product3;
    throw new ArgumentException("Unknown product: " + productName);
}

private static Fund ConvertFundName(string fundName)
{
    if (fundName == "fund1")
        return Fund.Fund1;
    if (fundName == "fund2")
        return Fund.Fund2;
    if (fundName == "fund3")
        return Fund.Fund3;
    throw new ArgumentException("Unknown fund: " + fundName);
}

Simplification: The ConvertDict method can be rewritten using a single LINQ-expression like this:

private static Dictionary<T, double> ConvertDict<T>(
    Dictionary<string, double> dict,
    Func<string, T> convertKey)
{
    return dict.ToDictionary(
        kvp => convertKey(kvp.Key),
        kvp => kvp.Value);
}

Simplification: And the convert methods would be nicer using a switch statement:

private static Product ConvertProductName(string productName)
{
    switch (productName)
    {
        case "prod1": return Product.Product1;
        case "prod2": return Product.Product2;
        case "prod3": return Product.Product3;
        default:
            throw new ArgumentException("Unknown product: " + productName);
    }
}

Then you call it this way:

    Dictionary<Product, double> prodDictA = ConvertDict<Product>(rawProdDictA, ConvertProductName);
    Dictionary<Product, double> prodDictB = ConvertDict<Product>(rawProdDictB, ConvertProductName);
    Dictionary<Fund, double> fundDictA = ConvertDict<Fund>(rawFundDictA, ConvertFundName);

Simplification: Or, you can even leave out the generic type and let the compiler figure out the <T> given the convertKey func you use:

    Dictionary<Product, double> prodDictA = ConvertDict(rawProdDictA, ConvertProductName);
    Dictionary<Product, double> prodDictB = ConvertDict(rawProdDictB, ConvertProductName);
    Dictionary<Fund, double> fundDictA = ConvertDict(rawFundDictA, ConvertFundName);

The point of generics is to handle all types the same way. In your case clients won´t expect the following call to fail at runtime:

var u = ConvertToDict<MyUnrelatedType>(rawProdDictA)

because there´s no restriction on the generic argument. In other words:w hen you need to switch on the type, there´s not much generic here, is it?

Apart from this let´s see the client-side here. When you want to call the method, you´d have to provide the generic type-parameter:

var prodDictA = ConvertToDict<Product>(rawProdDictA)

So where is the benefit over this?

var prodDictA = ConvertProductToDict(rawProdDictA)

This makes the purpose of the method pretty clear. So from the client-view there´s no benefit, as you´d have to know the type T anyway.

Let´s see the server-side (the persepctive from inside your method). Inside it we have three completely distinct branches that cover nothing generic. So instead of pretending your method really works for any type just create three methods:

private static Dictionary<Product, double> ConvertProductToDict(Dictionary<string, double> dict)
{
    var returnDict = new Dictionary<Product, double>();
    foreach(var keyValuePar in dict)
        returnDict.Add(CustomProductEnumConverter(keyValuePar.Key), keyValuePar.Value);
    return returnDict;
}

If I were you, I'd implement something like that:


using System;
using System.Collections.Generic;
using System.Linq;

public enum Product { Milk, Coffee, Juice };
public enum Fund { Fund1, Fund2, Fund3 };
//...and lots of different enums

public class Program
{
    public static void Main()
    {
        Console.WriteLine("Hello World");

        //Assume that this dictionary contains your elements
        var dict = new Dictionary<string, double>();

        Dictionary<Product, double> result = dict.ConvertDict(CustomProductEnumConverter);

    }

    private static Product CustomProductEnumConverter(string productName)
    {
        //No clear link between enum name and string value...
        if (productName == "base012")
            return Product.Coffee;
        if (productName == "defa341")
            return Product.Milk;
        if (productName == "urak451")
             return Product.Juice;
        //implement your own logic
        else throw new Exception();
    }
}

public static class DictionaryExtensions 
{
    public static Dictionary<T, double> ConvertDict<T>(this Dictionary<string, double> dict, Func<string, T> converter)
    {
        var returnDict = new Dictionary<T, double>();
        foreach (var keyValuePar in dict)
            returnDict.Add(converter(keyValuePar.Key), keyValuePar.Value);
        return returnDict;
    }   
}


The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM