2

Awkward title I know, this is best explained in code. Given a set of classes:

public abstract class MyBaseType
{
    public string Message { get; set; }
}

public class MySuperType : MyBaseType
{
    public string AdditionalInfo { get; set; }
}

public class MyOtherSuperType : MyBaseType
{
    public DateTime Started { get; set; }
    public DateTime Finished { get; set; }
}

Is there a way to write a generic method that calls a non-generic method passing the generic type whilst interpreting the passed type as its actual type and not the base type. That is, I want to write something like this:

public void OutputFormattedTypeInfo<T>(T theType) where T : MyBaseType
{
    OutputFormattedTypeInfo(theType as T);
}

public void OutputFormattedTypeInfo(MySuperType theType)
{
    System.Console.WriteLine(String.Format("{0} and {1}", theType.Message, theType.AdditionalInfo));
}

public void OutputFormattedTypeInfo(MyOtherSuperType theType)
{
    System.Console.WriteLine(String.Format("{0} - Start: {1}, End: {2}", theType.Message, theType.Started, theType.Finished));
}

But obviously theType as T is interpreted as the base type. I know I can use reflection like this:

Type type = typeof(MyBaseTypeDisplayFormatter);
    MethodInfo method = type.GetMethod(
                                    "FormatSpecific",
                                    BindingFlags.Instance | BindingFlags.NonPublic,
                                    null,
                                    new[] { update.GetType() },
                                    null);

    return (MyBaseTypeDataItem)method.Invoke(this, new object[] { update });

but it just feels inelegant. Is there a better way?

Aliostad
  • 80,612
  • 21
  • 160
  • 208
Wolfwyrd
  • 15,716
  • 5
  • 47
  • 67

2 Answers2

1

The problem here is you are not really expressing a generality. You have to implement OutputFormattedTypeInfo for each and every base type, so you might as well forget about the generic method and simply use the overloads (you do not need generics here):

public void OutputFormattedTypeInfo(BaseType theType)
{
    // ...
}

public void OutputFormattedTypeInfo(MySuperType theType)
{
    // ...
}

public void OutputFormattedTypeInfo(MyOtherSuperType theType)
{
    // ...
}
Aliostad
  • 80,612
  • 21
  • 160
  • 208
  • -1 This will always call the method with the base class overload, never the specialised class. – Ed James Apr 15 '11 at 12:22
  • You are wrong my friend. If you pass a Giraffe and you have overloads for Giraffe and Animal,it will use the correct overload. – Aliostad Apr 15 '11 at 12:23
  • Yes, but if you're dealing with a collection of them, say IEnumerable, it will go for animal whether it's a giraffe or a wombat. In the case of a single T that is of the correct derived type you are correct, however. – Ed James Apr 15 '11 at 12:36
1

As Aliostad said, what your trying to do is no longer generic and it would be better to just use overloads. It looks like you are trying to do something similar to template specialization in C++, where depending on the generic type it calls different methods.

Here is an example where I implemented a sort of generic specialization using reflection, you might be able to apply a similar pattern if overloading method wont work for you. If you can cache the results of reflection and only call GetMethod once then it turns out to be not too slow. Inside a class generic by T there is a method that calls:

if (_serializeDataToStream == null)
    _serializeDataToStream = (Action<BinaryWriter, int, T[]>)GetTypeSpecificSerializationMethod();

_serializeDataToStream(writer, _size, _data);

Where the GetTypeSpecific method uses reflection to create a delegate

/// <summary>
/// Returns a delegate that points at the static type specific serialization method
/// </summary>
/// <returns></returns>
private Delegate GetTypeSpecificDeserializationMethod()
{
    if (typeof(T) == typeof(double))
    {
        MethodInfo method = this.GetType().GetMethod("DeserializeDouble", BindingFlags.Static | BindingFlags.NonPublic);
        return Delegate.CreateDelegate(typeof(Action<BinaryReader, int, T[]>), method);
    }
    else if (typeof(T) == typeof(ushort))
    {
        MethodInfo method = this.GetType().GetMethod("DeserializeUshort", BindingFlags.Static | BindingFlags.NonPublic);
        return Delegate.CreateDelegate(typeof(Action<BinaryReader, int, T[]>), method);
    }
    else if (typeof(T) == typeof(DateTime))
    {
        MethodInfo method = this.GetType().GetMethod("DeserializeDateTime", BindingFlags.Static | BindingFlags.NonPublic);
        return Delegate.CreateDelegate(typeof(Action<BinaryReader, int, T[]>), method);
    }
    else if (typeof(T) == typeof(bool))
    {
        MethodInfo method = this.GetType().GetMethod("DeserializeBool", BindingFlags.Static | BindingFlags.NonPublic);
        return Delegate.CreateDelegate(typeof(Action<BinaryReader, int, T[]>), method);
    }

    throw new NotImplementedException("No deserialization method has been setup for type " + typeof(T).FullName);
}

/// <summary>
/// Serialize double[] to BinaryWriter
/// </summary>
/// <param name="writer"></param>
/// <param name="size"></param>
/// <param name="data"></param>
private static void SerializeDouble(BinaryWriter writer, int size, double[] data)
{
    for (int i = 0; i < size; i++)
    {
        writer.Write(data[i]);
    }
}
BrandonAGr
  • 5,827
  • 5
  • 47
  • 72