Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dictionary<T, Func>: how to use T as Func's generic 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 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");
    }
}
like image 778
Kilazur Avatar asked Oct 29 '25 22:10

Kilazur


1 Answers

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);
like image 103
anaximander Avatar answered Oct 31 '25 10:10

anaximander



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!