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

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 that says that the class that implements it must provide conversion methods for all the custom types it uses (be it an enumeration or whatever).

Is it possible to replace object

with Func

generic types with the corresponding type of the dictionary key (so it is impossible to have two unmatching types)?

I think this is not possible, but the alternatives are a little annoying (using dynamic

or object

, creating a specialized dictionary ...).


edit 1: Imaginary use case

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");
    }
}

      

+3


source to share


2 answers


It's a little more complicated, but it works.

First, you need to encapsulate the transformations Func

inside the classes so that you can handle them more easily and without exposing all of your arguments to a different type. Then you need to define interfaces or base classes to hide the various generic arguments from the places they cause problems and allow you to put different converters in the same collection. Then you will need ways in which the various converters can signal what types they operate on without directly using those type arguments. Then you just need to wrap the whole thing in a class with a method that will find the converter you want on demand.

I'll get through this.

First, this base class will be our way of handling the converter without worrying about its general type arguments, but still know what types it works with.

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

      

We now inherit from this base class. This is the class that does the actual conversion work; you can instantiate it with a lambda that does whatever conversion operation you need. Note that it implements the properties 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);
    }
}

      



We now need one place to store all of this, so the consumer code has an entry point. For simplicity's sake, I took it as a flat collection of converters, and then passed them all into nested dictionaries so that I could later execute queries without clicking 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 when you need it you can just use it like this:

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

      

+1


source


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

The implementation itself can use any construct to provide a converter. Event if ... else, but a type dictionary can be used to use a dictionary under the hood Dictionary<Type, Delegate>

, where strongly typed converters can be added. The example below uses a helper function set<T>

to ensure that the dictionary is set as expected.



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 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");
}

      

+2


source







All Articles