Reputation: 157
I am building a helper function to map properties of an object to an object, which knows to which UI component the property is bound and how to convert their values back and forth.
Now I need the map itself, to map property names (of type String) to their ModelFieldItems, which contain a generic converter (eg StringToBoolConverter). But due to the lack of the <?>
Operator, I'm used to from java, I can't simply write private Dictionary<String, ModelFieldItem> items = new Dictionary<String, ModelFieldItem<?,?>>();
where ModelFieldItem contains the type Information and the converter itself.
As a result I need to add specific Types if I want to add a ModelFieldItem to the map, but they differ in type.
I already tried to use dynamic
or object
as type parameters, but sooner or later I reach a point, where it does not work.
Using the trick from C# - Multiple generic types in one list I got the compiler happy, but I needed a typecast to access my converter logic, which led me to the following condition
if (item is ModelFieldItem<dynamic, dynamic>)
{
ModelFieldItem<dynamic, dynamic> dynamicItem = (ModelFieldItem<dynamic, dynamic>)item;
Which always resolves to false
.
EDIT
The Method, which starts the conversion, does not know, which types to convert, which is desired. We are using a loop to iterate over the properties and start their corresponding converters. So casting at the point of conversion can not be done with the distinct types, because we can not know them.
Our converters are as easy as they can be, inheriting from the following abstract class
public abstract class TypedConverter<A, B>
{
public abstract B convertFrom(A value);
public abstract A convertTo(B value);
}
As mentioned before, I'm from a java background, so what I want to achieve looks roughly like the following in Java
private Map<String, ModelFieldItem<?,?>> converters = new HashMap<>();
and I would use it roughly like
converters.put("key", new Converter<String, Boolean>());
How can I achieve this in C#?
Upvotes: 0
Views: 786
Reputation: 1561
Usually when you encounter this type of problem, you have to use an abstraction of your class that doesn't require the generic types.
If your ModelFieldItem
implement an interface without generic parameters, you can use it.
var _dict = new Dictionary<String, IModelFieldItem>()
{
new Converter<string, bool>() // If it's implement IModelFieldItem
};
(YouWillHaveToCastUnlessYouUseDynamicType)_dict[key].Convert("true");
Otherwise, another way would be to replace the ModelFieldItem
from Dictionary<String, ModelFieldItem>
by object
or dynamic
, then you can cast it when accessing to the value from your dictionary.
var _dict = new Dictionary<String, object>()
{
new Converter<string, bool>()
};
// You can avoid the cast too by using dynamic
// But it will cost you some perf
((Converter<string, bool>)_dict[key]).Convert("true");
if you know the type that you want.
You can do something like that:
var _dict = new Dictionary<String, object>()
{
new Converter<string, bool>()
};
public void Convert<TToConvert, TConverted>(string key, TToConvert valueToConvert, out TConverted valueConverted)
{
valueConverted = (T)_dict[key].Convert(valueToConvert);
}
bool value;
Convert("Key", "true", out value);
Here an other example of what you could do:
public static void Convert<TToConvert, TConverted>(TToConvert valueToConvert, out TConverted valueConverted)
{
// You should put the dictionary outside of the method
// To avoid to instance it, each time you call this method
var dict = new Dictionary<Type, Func<object, object>>()
{
{ typeof(Tuple<string, int>), x => int.Parse((string)x) },
{ typeof(Tuple<string, bool>), x => bool.Parse((string)x) }
};
valueConverted = (TConverted)dict[typeof(Tuple<TToConvert, TConverted>)](valueToConvert);
}
static void Main(string[] args)
{
bool boolTest;
Convert("false", out boolTest);
Console.WriteLine(boolTest);
int intTest;
Convert("42", out intTest);
Console.WriteLine(intTest);
Console.ReadKey();
}
Obvisouly, you should try if you can convert your type first and also if the conversion succeed. Finally make Convert
return an boolean to know if it succeed or not.
But at least as you can see, there is no more string key
required to make the conversion and it might interests you.
You also have to be sure that your variables have the right type when you pass them to the method, otherwise you will search for the wrong key.
Reflection solution:
With the above method you can do something like that:
static void Main(string[] args)
{
object[] parameters = new object[] { "false", true };
typeof(Program).GetMethod("Convert")
// Be sure that the types will create a valid key
.MakeGenericMethod(new Type[] { parameters[0].GetType(), parameters[1].GetType() })
// Change null to your instance
// if you are not calling a static method
.Invoke(null, parameters);
// parameters[1] is an out parameter
// then you can get its value like that
Console.WriteLine(parameters[1]);
Console.ReadKey();
}
With properties, that should look like this:
object[] parameters = new object[]
{
propertyToRead.GetValue(objectToRead),
propertyToSet.GetValue(objectToSet)
};
typeof(MapperObject).GetMethod("Convert")
.MakeGenericMethod(new Type[]
{
propertyToRead.PropertyType,
propertyToSet.PropertyType
})
.Invoke(mapperInstance, parameters);
propertyToSet.SetValue(objectToSet, parameters[1]);
You may need to adjust it a bit, since I didn't try to compile it
I could give another solution, but I neither know what is your inheritance architecture nor how your Converter
works.
Upvotes: 1