I'm coming to C# from a Java background and keep bumping in to the same sort of problem with generics that would be trivial to solve in Java.
Given the classes:
interface IUntypedField { }
class Field<TValue> : IUntypedField { }
interface IFieldMap
{
void Put<TValue>(Field<TValue> field, TValue value);
TValue Get<TValue>(Field<TValue> field);
}
I'd like to write something like:
class MapCopier
{
void Copy(IEnumerable<IUntypedField> fields, IFieldMap from, IFieldMap to)
{
foreach (var field in fields)
Copy(field, from, to); // <-- clearly doesn't compile as field is IUntypedField not Field
}
void Copy<TValue>(Field<TValue> field, IFieldMap from, IFieldMap to)
{
to.Put(field, from.Get(field));
}
}
In Java this would simple to solve as the fields collection would be an Iterable<Field<?>>
and you could call Copy(Field, IFieldMap, IFieldMap)
directly.
In C# I find myself doing switch/cast for all possible values of TValue
(which smells horrifically, having to add a case for every type you add is clearly a bug waiting to happen, and only feasible if the set of types are finite):
foreach (var field in fields)
{
switch (field.Type) // added an enum to track the type of Field's parameterised type
{
case Type.Int: Copy((Field<int>)field, from, to); break;
case Type.Long: Copy((Field<long>)field, from, to); break;
...
}
}
The other option I sometimes do is to move the functionality in to the Field class, which again, stinks. It's not the responsibility of the field. At least this avoids the huge switch:
interface IUntypedField { void Copy(IFieldMap from, IFieldMap to); }
class Field<TValue> : IUntypedField
{
void Copy(IFieldMap from, IFieldMap to)
{
to.Put(this, from.Get(this));
}
}
...
foreach (var field in fields)
field.Copy(from, to);
If you could write polymorphic extension methods (i.e. the Copy methods in IUntypedField and Field above) then you could at least keep the code in alongside the class whose responsibility it is.
Am I missing some feature of C# that would make this possible. Or is there some functional pattern that could be used? Any ideas?
(One last thing, I'm currently stuck on .Net 3.5, so can't make use of any covariance/contravariance, but would still be interested to know how they would help here, if at all).
Here's a perfectly type-safe method that compiles lambdas to perform the copy:
static class MapCopier
{
public static void Copy(IEnumerable<IUntypedField> fields, IFieldMap from, IFieldMap to)
{
foreach (var field in fields)
Copy(field, from, to);
}
// cache generated Copy lambdas
static Dictionary<Type, Action<IUntypedField, IFieldMap, IFieldMap>> copiers =
new Dictionary<Type, Action<IUntypedField, IFieldMap, IFieldMap>>();
// generate Copy lambda based on passed-in type
static void Copy(IUntypedField field, IFieldMap from, IFieldMap to)
{
// figure out what type we need to look up;
// we know we have a Field<TValue>, so find TValue
Type type = field.GetType().GetGenericArguments()[0];
Action<IUntypedField, IFieldMap, IFieldMap> copier;
if (!copiers.TryGetValue(type, out copier))
{
// copier not found; create a lambda and compile it
Type tFieldMap = typeof(IFieldMap);
// create parameters to lambda
ParameterExpression
fieldParam = Expression.Parameter(typeof(IUntypedField)),
fromParam = Expression.Parameter(tFieldMap),
toParam = Expression.Parameter(tFieldMap);
// create expression for "(Field<TValue>)field"
var converter = Expression.Convert(fieldParam, field.GetType());
// create expression for "to.Put(field, from.Get(field))"
var copierExp =
Expression.Call(
toParam,
tFieldMap.GetMethod("Put").MakeGenericMethod(type),
converter,
Expression.Call(
fromParam,
tFieldMap.GetMethod("Get").MakeGenericMethod(type),
converter));
// create our lambda and compile it
copier =
Expression.Lambda<Action<IUntypedField, IFieldMap, IFieldMap>>(
copierExp,
fieldParam,
fromParam,
toParam)
.Compile();
// add the compiled lambda to the cache
copiers[type] = copier;
}
// invoke the actual copy lambda
copier(field, from, to);
}
public static void Copy<TValue>(Field<TValue> field, IFieldMap from, IFieldMap to)
{
to.Put(field, from.Get(field));
}
}
Note that this method creates the copying method on-the-fly rather than calling the Copy<TValue>
method. This is essentially inlining, and saves about 50ns per call by not having the extra call. If you were to make the Copy
method more complicated, it might be easier to call Copy
rather than creating an expression tree to inline it.
You can use reflection at least to avoid switch { }
. Covariance/contravariance from fw 4.0 won't help in this case. Maybe it is possible to benefit from using dynamic
keyword.
// untested, just demonstates concept
class MapCopier
{
private static void GenericCopy<TValue>(Field<TValue> field, IFieldMap from, IFieldMap to)
{
to.Put(field, from.Get(field));
}
public void Copy(IEnumerable<IUntypedField> fields, IFieldMap from, IFieldMap to)
{
var genericMethod = typeof(MapCopier).GetMethod("GenericCopy");
foreach(var field in fields)
{
var type = field.GetType().GetGenericArguments()[0];
var method = genericMethod.MakeGenericMethod(type);
method.Invoke(null, new object[] { field, from, to });
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With