7

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).

SimonC
  • 6,590
  • 1
  • 23
  • 40
  • I'm not sure I understand your pattern, can you provide an implementation of an IFieldMap? – Vadim Jan 03 '11 at 04:05
  • See http://stackoverflow.com/questions/527766/what-is-the-equivalent-of-java-wildcards-in-c-generics – Claus Jørgensen Jan 03 '11 at 04:22
  • @Yads, the implementation isn't really important here. As pointed it by siride, it would internally store things as objects and have to cast, but as far as the consumer of the interface is concerned, it's putting and getting real types. – SimonC Jan 03 '11 at 06:04
  • 1
    @Claus, the answer to that question assumes the user wants to work with a homogeneous list (like Danny Chen's answer) which is a different problem. – SimonC Jan 03 '11 at 06:05
  • The point was more that the reason you finds it easier in Java, is because you uses wildcards. Thus he should learn the C# "alternative" to Java wildcards. – Claus Jørgensen Jan 03 '11 at 13:48

5 Answers5

2

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 });
        }
    }
}
max
  • 33,369
  • 7
  • 73
  • 84
  • This is the least worse solution I've seen so far (I still reel seeing reflection used to invoke methods). I need to run some performance tests on this to see how badly it compares to leaving the functionality in the Field class. – SimonC Jan 03 '11 at 06:00
  • If you cache the methods (1 per type), you can probably do the reflection in microseconds. This should be fine if you're doing thousands of copies per second, but too slow if you're doing millions per second. – Gabe Jan 03 '11 at 07:15
  • I've done a quick benchmark of this with a cache of Type => MethodInfo (not `lock`ed as I wouldn't require it in my case). 10,000 invocations of the reflection based Copy with 10 fields takes ~550ms compared to ~30ms for the map Copy that calls Field.Copy. It's probably not a big enough difference to be noticeable in my case, but it's far from ideal. – SimonC Jan 03 '11 at 07:39
  • 1
    SimonC: You can also compile methods at runtime using `Expression` trees, which will run at "native" speed. I can post an example if you're interested. – Gabe Jan 03 '11 at 14:37
  • @Gabe, I've just tried an `Expression` tree approach and was blown away by the performance. Took an average of ~40ms for map copy (first one was ~80ms to compile all the expression trees). If you want to post an answer I'll mark that as accepted. – SimonC Jan 04 '11 at 03:13
  • SimonC: I couldn't reach 40ms, but I posted an `Expression` example. – Gabe Jan 04 '11 at 06:29
1

Since Java can only get away with this due to type erasure, why not emulate the erasure with non-generic versions? Here's an example:

interface IUntypedField { }
class Field<TValue> : IUntypedField { }

interface IFieldMap
{
    void Put<TValue>(Field<TValue> field, TValue value);
    TValue Get<TValue>(Field<TValue> field);
    void PutObject(IUntypedField field, object value); // <-- this has a cast
                                                 // to TValue for type safety
    object GetObject(IUntypedField field);
}

class MapCopier
{
    void Copy(IEnumerable<IUntypedField> fields, IFieldMap from, IFieldMap to)
    {
        foreach (var field in fields)
            Copy(field, from, to); // <-- now compiles because
                                   // it calls non-generic overload
    }

    // non-generic overload of Copy
    void Copy(IUntypedField field, IFieldMap from, IFieldMap to)
    {
        to.PutObject(field, from.GetObject(field));
    }

    void Copy<TValue>(Field<TValue> field, IFieldMap from, IFieldMap to)
    {
        to.Put(field, from.Get(field));
    }
}
Gabe
  • 84,912
  • 12
  • 139
  • 238
  • Unfortunately, the type erasure emulated method doesn't give the compile time check that Java would do. – SimonC Jan 04 '11 at 01:38
1

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.

Gabe
  • 84,912
  • 12
  • 139
  • 238
0

You have a problem: you are asking the compiler to make a decision that can only be made at runtime. Each instance in the enumerable set is supposed to provide the type for the generic parameter to Copy(). But that decision must be made at compile-time. This is a contradiction. Java can get away with it because of type erasure. Since you want the same behavior, your best bet is to make the second Copy() method be non-generic and instead just take as its first parameter IUnknownField or whatever base interface or class is suitable.

Elaborating on that, I have to ask what you are gaining by having the IFieldMapinterface have a generic Put() method. Internally, it must be storing the items in some sort of heterogeneous collection (which means that somewhere you will have to have a list or dictionary of objects). So the generics don't actually provide any type safety. You might as well just get rid of them and have the Put() and Get() methods, as well as Copy() in the MapCopier class either take object or some suitable base interface, such as IUnknownField. Because when you get down to it, that's all you are ever really working with.

siride
  • 200,666
  • 4
  • 41
  • 62
  • Regardless of how it's stored internally, the compiler is giving type safety for users of the interface. I'd much rather a class cast exception occurs within the map than further away from it where the value is being used (thus not where the bug is). – SimonC Jan 03 '11 at 06:02
  • @SimonC but the compiler *isn't* giving you any type safety at all. You have no constraints on your generic parameter. Furthermore, there is no way to have compile-time type checking for heterogeneous collections. Even though it is true that it is an implementation detail, you are not requiring, or even suggesting, that the entries in `FieldMap` are homogeneous. Since they aren't, you have to rely on runtime checking in your own code. The compiler can't do anything for you, at least not for the `Put()` case. In the `Get()` case, it's going to fail at about the same time and... – siride Jan 03 '11 at 06:32
  • ...for the same reasons as the non-generic case. – siride Jan 03 '11 at 06:34
  • Perhaps I should just cut to the chase: it's fine for the interface to have those methods. But it should also have non-generic versions that can be used by copiers and other bulk operations. – siride Jan 03 '11 at 06:38
  • The consumer of the interface will have some confidence that if they have a field `f = new Field()`, the compiler will enforce that they can't do `map.Put(f, "foo")` or `bool bar = map.Get(f)`. If anything is wrong with the implementation, an exception will be thrown within the implementation (and not at the point in the users code that the value from `map.Get` is used). If I allow non-generic `object` based methods, the consumers have less confidence on the types of values in the map. – SimonC Jan 03 '11 at 06:52
0

If you don't want to use Reflection, then you may not want to use generics. Here is a code that allows to do this:

interface IUntypedField { }

abstract class Field
{
    protected Field(Type type)
    {
        FieldType = type;
    }

    public Type FieldType { get; private set; }
}

class Field<TValue> : Field, IUntypedField {

    public Field()
        : base(typeof(TValue))
    {
    }
}

interface IFieldMap
{
    void Put(Field field, object value);
    object Get(Field field);
}

class MapCopier
{
    void Copy(IEnumerable<IUntypedField> fields, IFieldMap from, IFieldMap to)
    {
        foreach (var field in fields)
        {
            Field f = field as Field;
            if (f != null)
            {
                Copy(f, from, to);
            }
        }
    }

    void Copy(Field field, IFieldMap from, IFieldMap to)
    {
        to.Put(field, from.Get(field));
    }
}

In this code, each Field derives from a class that carries the field type dynamically (not using generics), but you still have the generic class. You will see this kind of pattern in the .NET Framework itself, notably in the System.ServiceModel class (around Channel/ChannelFactory types).

The drawback is in this case you must de-generize the IFieldMap interface. However, it's possible that when you implement it, you see that you could not work with generics there either.

Another possibility would be to add the FieldType property directly on the IUntypedField avoiding the abstract class Field.

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • I'm actually going for a hybrid approach where, as you suggest, I'm storing the Type in a field on IUntypedField, and as a Type parameter on Field. Where possible I'm using the generic interface as there's better compile time checking, but where I *have* to, I'm dropping back to runtime type checking with the non-generic interface. I was really hoping I'd missed some language feature or clever use of functional programming to do this, but alas it appears I haven't. – SimonC Jan 03 '11 at 09:34