Reputation: 354
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
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:
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
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
Reputation: 9238
Not sure if this works for you, but it is an option:
IHaveFromBytesMethod<T>
which defines a FromBytes(...)
instance method.Foo#
types implement this interface. (If you do not want this instance method to be visible immediately, you could implement the interface explicitly.)T
in your ListOfFoo<T>
class to implement this interface and to provide a parameterless constructor.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
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
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
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
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
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.
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