Dictionary<T, Func>: how to use T as Func's generic type?

≡放荡痞女 提交于 2019-12-07 15:57:33

问题


I didn't know how to express it clearly.

I have this interface:

interface IConverter
{
    Dictionary<Type, Func<string, object>> ConversionMethods { get; }
}

Basically, it defines a contract saying that a class implementing it should provide conversion methods for all the custom types it uses (be them enums or anything).

Is it possible to replace object in Func's generic types with its corresponding dictionary key's type (so it is impossible to have two unmatching types)?

I think it's not possible, but the alternatives are a bit annoying (using dynamic or object, creating a specialized dictionary...).


edit 1: Imaginary exemple of use

interface IConverter
{
    Dictionary<Type, Func<string, object>> GetConversionMethods();
}

enum A
{
    AA,AB,AC
}

enum B
{
    BA, BB, BC
}

class blah : IConverter
{
    public Dictionary<Type, Func<string, object>> GetConversionMethods()
    {
        var d = new Dictionary<Type, Func<string, object>>
        {
            {
                typeof(A),
                (s) =>
                    {
                        // here, I could return whatever I want because the 'Func' returns 'object'
                        return s == "AA" ? A.AA : s == "AB" ? A.AB : A.AC;
                    }
            },
            {
                typeof(B),
                (s) =>
                    {
                        // same
                        return s == "BA" ? B.BA : s == "BB" ? B.BB : B.BC;
                    }
            }
        };
        return d;
    }

    void blahah()
    {
        // and here, I also get an `object`, where I would like to have a A
        GetConversionMethods()[typeof(A)]("123");
    }
}

回答1:


This gets a bit complex, but it works.

First, you'll need to encapsulate the conversion Funcs inside classes, so that you can handle them more easily and without exposing all their differing type arguments. Then you'll need to define interfaces or base classes to hide the various generic arguments from places they'll cause problems, and allow you to put different converters in the same collection. Then, you'll need ways for the various converters to signal what types they work with without using those type arguments directly. Then you just need to wrap it all in a class with a method that'll find you the right converter on demand.

I'll walk you through it.

First, this base class will be our way to handle a converter without worrying about its generic type arguments, but still know what types it work with.

public abstract class OneWayTypeConverterBase : IConvertFromType, IConvertToType
{
    public abstract Type AcceptsType { get; }
    public abstract Type ReturnsType { get; }
}

Now we inherit from that base class. This is the class that does the actual work of converting; you can instantiate it with a lambda that does whatever conversion operation you need. Notice that it implements the properties we defined above.

public class OneWayTypeConverter<TSource, TTarget> : OneWayTypeConverterBase
{
    public OneWayTypeConverter(Func<TSource, TTarget> conversionMethod)
    {
        _conversionMethod = conversionMethod;
    }

    public override Type AcceptsType => typeof(TSource);
    public override Type ReturnsType => typeof(TTarget);

    private readonly Func<TSource, TTarget> _conversionMethod;

    public TTarget Convert(TSource sourceObject)
    {
        return _conversionMethod(sourceObject);
    }
}

Now we need a single place to hold all of this, so that consuming code has an entry point. For simplicity, I had it take in a flat collection of converters, and then file them all into nested dictionaries so that it can do the lookups later without having to call typeof all the time.

public class TypeConverter
{
    public TypeConverter(IEnumerable<OneWayTypeConverterBase> converters)
    {
        _converters = converters
            .GroupBy(x => x.AcceptsType)
            .ToDictionary(
                kSource => kSource.Key,
                vSource => vSource
                    .ToDictionary(kTarget => kTarget.ReturnsType, vTarget => vTarget));
    }

    private Dictionary<Type, Dictionary<Type, OneWayTypeConverterBase>> _converters;

    public TTarget ConvertType<TSource, TTarget>(TSource sourceObject)
    {
        Dictionary<Type, OneWayTypeConverterBase> baseConverters;

        if (_converters.TryGetValue(sourceObject.GetType(), out baseConverters))
        {
            OneWayTypeConverterBase baseConverter;

            if (baseConverters.TryGetValue(typeof(TTarget), out baseConverter))
            {
                OneWayTypeConverter<TSource, TTarget> converter = baseConverter as OneWayTypeConverter<TSource, TTarget>;

                if (converter != null)
                {
                    return converter.Convert(sourceObject);
                }
            }

            throw new InvalidOperationException("No converter found for that target type");
        }
        else
        {
            throw new InvalidOperationException("No converters found for that source type");
        }
    }
}

So now, you can set it up like this:

var converter = new TypeConverter(new List<OneWayTypeConverterBase>
{
    new OneWayTypeConverter<int, string>(x => $"The number was {x}"),
    new OneWayTypeConverter<int, bool>(x => x != 0),
    new OneWayTypeConverter<bool, string>(x => $"The bool was {x}")
});

and then whenever you need it, you can just use it like this:

var result = converter.ConvertType<int, string>(4);



回答2:


It depends in how far you can change the signatures, but at the least the interface can enforce using a generic type. How the converters are added strongly typed is then the responsibility of the implementer and never of the caller.

The implementation itself can use any construction to provide the converter. Event if...else, but to use a dictionary under the hood, a dictionary of the type Dictionary<Type, Delegate> can be used, where the converters can be added strongly typed. The example below uses a helper set<T> function to ensure the dictionary is set in the expected manner.

interface IConverter
{
    Func<string,T> GetConverter<T>(); //the method returned is always strongly typed, so the caller is never responsible for type checking
}

enum A{AA,AB,AC}    
enum B{BA, BB, BC}

class blah : IConverter
{
    public Func<string,T> GetConverter<T>()
    {
        if(methods.TryGetValue(typeof(T), out var fn)) //side note, out var fn will not work in older visual studio versions. In that case declare fn before this line
            return (Func<string,T>)fn; //the set<T> method ensures that this conversion is safe
        throw new NotImplementedException(); 
    }

    public blah()
    {
        set<A>(s => s == "AA" ? A.AA : s == "AB" ? A.AB : A.AC); //copied from the example. Enum.Parse could perhaps be used instead
        set<B>(s => s == "BA" ? B.BA : s == "BB" ? B.BB : B.BC);
    }

    Dictionary<Type, Delegate> methods= new Dictionary<Type, Delegate>(); // Delegate can be used as a type to handle all lambda's. It's the implementers responsibility to handle with care. Something like the set<T> helper method is recommended

    void set<T>(Func<string,T> fn) //helper method to assign the strongly typed methods to the specific type
    {
        methods[typeof(T)] = fn;
    }
}

static void blahah()
{
    new blah().GetConverter<A>()("123");
}


来源:https://stackoverflow.com/questions/44072996/dictionaryt-func-how-to-use-t-as-funcs-generic-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!