Karsten Gutjahr
Karsten Gutjahr

Reputation: 354

Is there a workaround to use static methods by a generic class?

I have a rather simple problem, but there seems to be no solution within C#.

I have around 100 Foo classes where each implement a static FromBytes() method. There are also some generic classes which shall use these methods for its own FromBytes(). BUT the generic classes cannot use the static FromBytes() methods, because T.FromBytes(...) is illegal.

Do I miss something or is there no way to implement this functionality?

public class Foo1
{
    public static Foo1 FromBytes(byte[] bytes, ref int index)
    {
        // build Foo1 instance
        return new Foo1()
        {
            Property1 = bytes[index++],
            Property2 = bytes[index++],
            // [...]
            Property10 = bytes[index++]
        };
    }

    public int Property1 { get; set; }
    public int Property2 { get; set; }
    // [...]
    public int Property10 { get; set; }
}

//public class Foo2 { ... }
// [...]
//public class Foo100 { ... }

// Generic class which needs the static method of T to work
public class ListOfFoo<T> : System.Collections.Generic.List<T>
{
    public static ListOfFoo<T> FromBytes(byte[] bytes, ref int index)
    {
        var count = bytes[index++];
        var listOfFoo = new ListOfFoo<T>();
        for (var i = 0; i < count; i++)
        {
            listOfFoo.Add(T.FromBytes(bytes, ref index)); // T.FromBytes(...) is illegal
        }

        return listOfFoo;
    }
}

I think it would be unfair to choose an answer as accepted answer, after all answers and comments have contributed in different ways with their different views. It would be nice if someone writes a good overview about the different approaches with their pros and cons. That should be accepted after it helps future developers best.

Upvotes: 8

Views: 721

Answers (8)

Karsten Gutjahr
Karsten Gutjahr

Reputation: 354

This shall be the summary for all future viewers.

Since you cannot just call the method, the main problem is to get the delegate. There are two approaches:

  • via reflection
  • from the caller

You might think reflection is slow, but performance is not an issue. If the delegate is stored in a static field, it only needs to be done once per class, because generic types don't share static members.

Compile time verifiability is an issue. If you care much about compile time verifiability you should go with passing the delegate from the caller. If you care more about a clean method call you have to sacrifice the compile time verifiability.

PS: Some people have suggested to have a dictionary or a switch/case or if/else where the delegates are stored. This is something you shouldn't do. This has no advantage over storing the delegate in a static field of the generic class (generic types don't share static members).

Upvotes: 0

Karsten Gutjahr
Karsten Gutjahr

Reputation: 354

I'd like to thank you all guys, you gave me some insights to think about. I've considered each approach and thought about its pros and con and wrote the following code. What do you think about? I think it is a good compromise for usability, readability and performance. It only lacks the compiler check.

public class ListOfFoo<T> : System.Collections.Generic.List<T>
{
    private static readonly FromBytesFunc<T> BytesFromFunc = 
        (FromBytesFunc<T>)System.Delegate.CreateDelegate(
            typeof(FromBytesFunc<T>),
            typeof(T).GetMethod("FromBytes"));

    private delegate T2 FromBytesFunc<out T2>(byte[] bytes, ref int index);

    public static ListOfFoo<T> FromBytes(byte[] bytes, ref int index)
    {
        var count = bytes[index++];
        var listOfFoo = new ListOfFoo<T>();
        for (var i = 0; i < count; i++)
        {
            listOfFoo.Add(BytesFromFunc(bytes, ref index));
        }

        return listOfFoo;
    }
}

Upvotes: 0

gehho
gehho

Reputation: 9238

Not sure if this works for you, but it is an option:

  1. Define an interface IHaveFromBytesMethod<T> which defines a FromBytes(...) instance method.
  2. Make all Foo# types implement this interface. (If you do not want this instance method to be visible immediately, you could implement the interface explicitly.)
  3. All implementations of the instance method simply forward the call to the static method of that type.
  4. Restrict type parameter T in your ListOfFoo<T> class to implement this interface and to provide a parameterless constructor.
  5. When you need the static method, construct a new dummy object of type T using the parameterless constructor and then call the instance method on that dummy instance.

Interface:

public interface IHaveFromBytesMethod<T>
{
    T FromBytes(byte[] bytes, ref int index);
}

One of the Foo# classes:

public class Foo1 : IHaveFromBytesMethod<Foo1>
{
    public Foo1()
    {
        // ...
    }

    public static Foo1 FromBytes(byte[] bytes, ref int index)
    {
        // ...
    }

    public Foo1 FromBytes(byte[] bytes, ref int index)
    {
        // within the instance method simply call the static method
        return Foo1.FromBytes(bytes, ref index);
    }
}

The modified ListOfFoo<T> class:

// requires T to implement the interface and provide a parameterless ctor!
public class ListOfFoo<T> : System.Collections.Generic.List<T>
    where T : IHaveFromBytesMethod<T>, new()
{
    public static ListOfFoo<T> FromBytes(byte[] bytes, ref int index)
    {
        // create dummy instance for accessing static method via instance method
        T dummy = new T();
        var count = bytes[index++];
        var listOfFoo = new ListOfFoo<T>();
        for (var i = 0; i < count; i++)
        {
            // instead of calling the static method,
            // call the instance method on the dummy instance
            listOfFoo.Add(dummy.FromBytes(bytes, ref index));
        }

        return listOfFoo;
    }
}

Upvotes: 1

Servy
Servy

Reputation: 203821

The best option would be to simply accept the specific FromBytes function as a delegate to your generic FromBytes function. This avoids both the performance cost and lack of compile time verifiability that comes along with using reflection.

public delegate T FromBytesFunc<T>(byte[] bytes, ref int index);
public static List<T> FromBytes<T>(byte[] bytes, ref int index,
    FromBytesFunc<T> function)
{
    var count = bytes[index++];
    var listOfFoo = new List<T>();
    for (var i = 0; i < count; i++)
    {
        listOfFoo.Add(function(bytes, ref index));
    }

    return listOfFoo;
}

Note that if you make the method, rather than the class it's in, generic, you can get the compiler to infer the generic argument. It could be called like so:

var list = SomeClass.FromBytes(bytes, ref index, Foo1.FromBytes);

Upvotes: 5

Kevin DiTraglia
Kevin DiTraglia

Reputation: 26058

Could you just implement the static FromBytes method generically in a utility class? Then do something like this?

listOf.Add(Utility.FromBytes<T>(bytes, ref index));

That utility method might be a bit ugly if each methods FromBytes is a lot different, but it wouldn't be too bad, it's all code that used to live in each class anyway.

Something like this:

public static class Utility
{
    public static T FromBytes<T>(byte[] bytes, ref int index)
    {
          if (typeof(T) is Foo1)
          {
               return Foo1.GetBytes(bytes, ref index);
          }
          //etc....
    }
}

You could also hide that reflection code here as pointed out by other answers. Unfortunately there doesn't seem to be a very clean way to do this, but hiding the messy stuff in a utility method may be the best option.

Upvotes: 2

Alessandro D&#39;Andria
Alessandro D&#39;Andria

Reputation: 8868

You can try something like this:

sealed class RetType
{
    public object Value
    {
        get;
        private set;
    }

    public int Index
    {
        get;
        private set;
    }

    public RetType(object value, int index)
    {
        Value = value;
        Index = index;
    }
}

public class ListOfFoo<T> : System.Collections.Generic.List<T>
{
    static readonly Dictionary<Type, Func<byte[], int, RetType>> dic = new Dictionary<Type, Func<byte[], int, RetType>>
    {
        {
            typeof(Foo1),
            new Func<byte[], int, RetType>((bytes, index) =>
            {
                var value = Foo1.FromBytes(bytes, ref index);

                return new RetType(value, index);
            })
        }
        // add here others Foo
    };

    public static ListOfFoo<T> FromBytes(byte[] bytes, ref int index)
    {
        var count = bytes[index++];
        var listOf = new ListOfFoo<T>();
        for (var i = 0; i < count; i++)
        {
            var o = dic[typeof(T)](bytes, index);

            listOf.Add((T)o.Value);

            index = o.Index;
        }

        return listOf;
    }
}

You build a lookup to finde the method you want to call to build the instance.

Upvotes: 1

svick
svick

Reputation: 244767

You can use reflection to find the method on that type and then build a delegate for it. Something like:

delegate T FromBytesFunc<T>(byte[] bytes, ref int index);

public static ListOfFoo<T> FromBytes(byte[] bytes, ref int index)
{
    FromBytesFunc<T> fromBytes =
        (FromBytesFunc<T>)Delegate.CreateDelegate(
            typeof(FromBytesFunc<T>), typeof(T).GetMethod("FromBytes")

    var count = bytes[index++];
    var listOf = new ListOfFoo<T>();
    for (var i = 0; i < count; i++)
    {
        listOf.Add(fromBytes(bytes, ref index));
    }

    return listOf;
}

Upvotes: 1

Smeegs
Smeegs

Reputation: 9224

The problem is that you're trying to use FromBytes as an extension method when it's not. From what I gather, you're trying to call the appropriate FromBytes for whatever <T> is, as defined in whatever class you've already created for T.

What you need to do is call the method via reflection.

Try looking at some of these threads for help on how to accomplish this.

Use Reflection to call generic method on object instance with signature: SomeObject.SomeGenericInstanceMethod<T>(T argument)

How do I use reflection to call a generic method?

Calling generic method using reflection in .NET

How to call generic method with a given Type object?

Upvotes: 3

Related Questions