ThePerplexedOne ThePerplexedOne - 1 month ago 9
C# Question

Using 'params' keyword to include single values as well as a value collection

I know the title of the question isn't very clear, but let me try to explain as best as I can.

I'm working on an application that modifies bytes of a file. I have two classes for this:

Mod
and
Action
.

An
Action
is stored in an instance of
Mod
, this tells the application which bytes to modify at which specific address:

public class Action
{
public Action(int address, params byte[] bytes)
{
Address = address;
Bytes = bytes;
}

public int Address { get; }
public byte[] Bytes { get; }
}
public class Mod
{
public Mod(string name, List<Action> actions)
{
Name = name;
Actions = actions;
}

public string Name { get; }
public List<Action> Actions { get; }
}


Now, there are some bytes that I wish to write, which become rather repetitive, so instead of assigning them to
bytes
when creating an instance of
Action
, I have stored said byte array inside a static class called
OpCodes
:

public static class OpCodes
{
public static byte[] BX_LR = {0x70, 0x47};
}


So now what I'm trying to achieve is the following:

new Action(0x1AF0F0, 0x01, 0x20, OpCodes.BX_LR) //Return 1. (true)


As you can see, I want to use bytes
0x01
,
0x20
and then I also want to use the value of
BX_LR
, however, the compiler doesn't like that.

I get this:


Argument 4: cannot convert from 'byte[]' to 'byte'


How can I achieve this?

Answer

I conjuted this in LinqPad:

void Main()
{
    var x = new Action(0x1AF0F0, 0x01, 0x20, OpCode.BX_LR); //Return 1. (true)
    Console.WriteLine(x);
}

public class Action
{
    public Action(int address, params OpCode[] bytes)
    {
        Address = address;
        Bytes = OpCode.Flatten(bytes);
    }

    public int Address { get; set; }
    public byte[] Bytes { get; set; }
}

public class Mod
{
    public Mod(string name, List<Action> actions)
    {
        Name = name;
        Actions = actions;
    }

    public string Name { get; set; }
    public List<Action> Actions { get; set; }
}

public class OpCode
{
    private byte[] _value;

    private OpCode(byte[] value)
    {
        _value = value;
    }

    public static byte[] BX_LR = {0x70, 0x47};

    public static implicit operator OpCode (byte value)
    {
        return new OpCode(new []{value});
    }

    public static implicit operator OpCode (byte[] value)
    {
        return new OpCode(value);
    }

    public static byte[] Flatten (OpCode[] opCodes)
    {
        var result = new List<byte>(opCodes.Length);
        foreach (var opCode in opCodes)
        {
            result.AddRange(opCode._value);
        }
        return result.ToArray();
    }
}

The output in LinqPad is as follows:

Address
1765616 
Bytes
01 20 70 47 

The code is using the class OpCode (I renamed OpCodes to singular) as intermediary between byte[] and byte. It makes for nice invocation.

The idea is that the constructor of Action takes a params of OpCode and any byte or byte[] will implicitly be casted to OpCode so you can just put bytes in the call... then in the constructure you can flat the structure to a single byte[] with a dedicated call.

Note: I added set; to the properties because of LinqPad's complains.


The benefits or this are in readability of the call, yet as you can see there is an overhead... that overhead will only make sense for long series of bytes.

Btw. you can make _value readonly, I would also suggest to make OpCode sealed a struct. The reason is that using OpCode as presented in the code will be created a bunch of short lived objects, but if they are struct then they will not load the garbage collector. Also, don't allow for derivec types. Edit: drawback of the struct is that you can't control the default constructor, and that will initilize it with _values as null.

Another suggestion si to define the public static fields as readonly (don't allow to replace them) and OpCode, that will save a cast and will prevent tampering the array.

Comments