I have an object that provides several functions to write and read data from a packet, something like this:
class Packet
{
void Write(int value) {/*...*/}
//...
int ReadInt() {/*...*/}
bool ReadBool() {/*...*/}
string ReadString() {/*...*/}
Vector3 ReadVector3() {/*...*/}
}
this class stores a byte[]
that is sent over the network. If I want to access data previously written, I can do it like this
void MyFunction(Packet packet)
{
int myInt = packet.ReadInt();
bool myBool = packet.ReadBool();
string myString = packet.ReadString();
Vector3 myVector3 = packet.ReadVector3();
//... do stuff
}
I was wondering if there is some syntactic sugar that would allow me to define a function taking a variable number of parameters of different types, detect which dynamic types they are at runtime, call the appropriate function and then return the parameter, something like this:
class Packet
{
//...
void ReadAll(out params object[] objects);
}
void MyFunction(Packet packet)
{
packet.ReadAll(out int myInt, out bool myBool, out string myString, out Vector3 myVector3);
//... do stuff with myInt, myBool, myString, myVector3
}
I looked at params
with an object[]
, the out
keyword, generics, and Convert.ChangeType()
but I couldn't get anything to work so far. I'm not sure this is even possible, and if it is, if the runtime cost of reflection will highly outweigh the benefits of simpler/less code for something used as frequently as network packets.
Thank you all.
You can try using generics and ValueTuples
:
public T Read<T>() where T:ITuple
{
return default(T); // some magic to create and fill one
}
and usage:
var (i, j) = Read<(int, int)>();
// or
var x = Read<(int i, int j)>();
As for reflection - you can cache reflection "results" per type:
public T Read<T>() where T : struct, ITuple
{
return TupleCreator<T>.Create(new ValueReader());
}
static class TupleCreator<T> where T : struct, ITuple
{
private static Func<ValueReader, T> factory;
static TupleCreator()
{
var fieldTypes = typeof(T).GetFields()
.Select(fi => fi.FieldType)
.ToArray();
if(fieldTypes.Length > 7)
{
throw new Exception("TODO");
}
var createMethod = typeof(ValueTuple).GetMethods()
.Where(m => m.Name == "Create" && m.GetParameters().Length == fieldTypes.Length)
.SingleOrDefault() ?? throw new NotSupportedException("ValueTuple.Create method not found.");
var createGenericMethod = createMethod.MakeGenericMethod(fieldTypes);
var par = Expression.Parameter(typeof(ValueReader));
// you will need to figure out how to find your `read` methods
// but it should be easy - just find a method starting with "Read"
// And returning desired type
// I can do as simple as:
var readMethod = typeof(ValueReader).GetMethod("Read");
var readCalls = fieldTypes
.Select(t => readMethod.MakeGenericMethod(t)) // can cache here also but I think not needed to
.Select(m => Expression.Call(par, m));
var create = Expression.Call(createGenericMethod, readCalls);
factory = Expression.Lambda<Func<ValueReader, T>>(create, par).Compile();
}
public static T Create(ValueReader reader) => factory(reader);
}
class ValueReader
{
public T Read<T>()
{
return default; // to simulate your read
}
}
Console.WriteLine(Read<(int i, double j)>()); // prints "(0, 0)"
NB
Also you can just implement the Read<T>
method with "caching" the reflection as done here.
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