Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic parameter list of different out types in C#

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.

like image 333
SimoGecko Avatar asked Oct 14 '25 14:10

SimoGecko


1 Answers

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.

like image 84
Guru Stron Avatar answered Oct 17 '25 05:10

Guru Stron



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!