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");
}
}
This gets a bit complex, but it works.
First, you'll need to encapsulate the conversion Func
s 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);
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