Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

List of generic delegates (e.g. System.Converter<TInput, TOutput>)

Tags:

c#

generics

Is it possible to create a list of generic delegates, like System.Converter<TInput, TOutput>?

In java that could be List<Converter<?,?>>, but with Java's type erasure that's not very useful. Seems like in C# it should be possible to query the generic types after retrieval from the list and use the obtained instance to perform the desired conversion.

Elaborating on the question:
I have user supplied data in user provided formats which I know nothing about at compile time. Algorithms in my program know how to work with its own specific data types, of course. The goal is to have an equiavalent of Java's List<Converter<?,?>> where Converters can be installed (by me, or users) and queried automatically by the program to see if user's data can be converted to the required format for an algorithm.

like image 342
Dmitry Avtonomov Avatar asked Dec 17 '19 07:12

Dmitry Avtonomov


2 Answers

This is very rudimentary and is only intended to show the basic concept.

It has:

  • A dictionary keyed on a 2-tuple of input and output types to hold converter instances.
  • A Register method to use for registering converters. Registering the same pair of types overwrites a previously registered converter. You can easily change it to be a no-op or an exception.
  • A Convert method to call a registered converter. It would be very trivial to create a TryConvert method if you needed it.
public static class ConverterContainer
{
    private static readonly Dictionary<(Type, Type), Delegate> _converters = new Dictionary<(Type, Type), Delegate>();

    public static void Register<TInput, TOutput>(Func<TInput, TOutput> converter)
    {
        if (converter is null)
            throw new ArgumentNullException(nameof(converter));

        _converters[(typeof(TInput), typeof(TOutput))] = converter;
    }

    public static TOutput Convert<TInput, TOutput>(TInput input)
    {
        if (_converters.TryGetValue((typeof(TInput), typeof(TOutput)), out var del))
        {
            Func<TInput, TOutput> converter = (Func<TInput, TOutput>)del;

            return converter(input);
        }

        throw new InvalidOperationException("Converter not registered.");
    }
}

What it does not have:

  • thread safety. This is left as an exercise to a serious implementor.
  • possibly other things I didn't take the time to consider.

How to use it:

At startup of your application, register converters, like registering services for dependency injection.

ConverterContainer.Register<long, int>(l => (int)l);
// ... etc.

And wherever you want to perform conversion between a registered pair of input/output types:

int x = ConverterContainer.Convert<long, int>(1000L)

Unfortunately, you do have to specify both type arguments here.


Addition from the OP:

To not have to specify the input parameter type (which is kind of the point of having a dynamic list of available conversions) use the following additional method in the sample ConverterContainer above (as @madreflection has suggested in the comments himself) :

public static TOutput Convert<TOutput>(object toConvert) {
    if (toConvert is null)
        throw new ArgumentNullException(nameof(toConvert));

    if (Converters.TryGetValue((toConvert.GetType(), typeof(TOutput)), out Delegate conv)) {
        object o = conv.DynamicInvoke(toConvert);
        return (TOutput) o;
    }

    throw new InvalidOperationException($"Converter not registered for types: {toConvert.GetType().Name} -> {typeof(TOutput).Name}");
}

You can now throw in any random object instance and see if the conversion to your desired type is possible.

like image 196
madreflection Avatar answered Oct 14 '22 09:10

madreflection


In C# all the converters in the list will share the same type parameters (TInput, TOutput). Unless you have a non generic interface IConverter, you will not be able to store multiple converter in anything else than a list of object.

You can anyway try to cast the elements into what you need like it is done here (with a dictionary). Hirerate trough an heterogeneous and type-safe dictionary

like image 31
Orace Avatar answered Oct 14 '22 11:10

Orace