What is the most efficient way to define a property using Get/Set with a switch for versions?

Viewed 69

I am writing a program to read and write memory from another program, however there are two different versions and the memory values have an offset in one version, but not in the second, so depending on the setting the user has selected (v1 or v2) I need to determine which address to read/write.

As the moment my code is as below, but I dont feel this is very effecient, and replicating this same code makes the class very long when it really doesnt need to be.

Is there a more efficient way of doing all of this:

public static int Property
{
    get
    {
        switch (Version)
        {
            case Version.v1:
                return Memory.ReadMemory<int>(0xB7CE50 + 0x2680);
            case Version.v2:
                return Memory.ReadMemory<int>(0xB7CE50);
            default:
                return 0;
        }
    }
    set
    {
        switch (Version)
        {
            case Version.v1:
                Memory.WriteMemory<int>(0xB7CE50 + 0x2680, value);
                break;
            case Version.v2:
                Memory.WriteMemory<int>(0xB7CE50, value);
                break;
            default:
                break;
        }
    }
}

Not all addresses need this offset, but most do, so I also need to account for this, hence I cant just add the 0x2680 value if v2

2 Answers

One obvious way to approach this is to bring the knowledge of what lives where into a central place. You might want to expand your definition of a Version to include memory addresses (see below), or you might want to include this information in some other class:

public class Version
{
    public int PropertyAddress { get; init; }

    public static Version Version1 { get; } = new()
    {
        PropertyAddress = 0xB7CE50 + 0x2680,
    };

    public static Version Version2 { get; } = new()
    {
        PropertyAddress = 0xB7CE50,
    };

    private Version() { }
}

(I've made use of C# 9 syntax here -- adjust if you're targetting an earlier language version).

You can then simplify your property to something like:

public static int Property
{
    get => Memory.ReadMemory<int>(Version.PropertyAddress);
    set => Memory.WriteMemory<int>(Version.PropertyAddress, value);
}

You could also e.g. put this in a MemoryAddresses class (if you wanted to keep Version as an enum), and do:

private static MemoryAddresses Addresses => Version switch
{
    Version.V1 => MemoryAddresses.V1,
    Version.V2 => MemoryAddresses.V2,
};

public static int Property
{
    get => Memory.ReadMemory<int>(Addresses.PropertyAddress);
    set => Memory.WriteMemory<int>(Addresses.PropertyAddress, value);
}

If you want to take advantage of the fact that some addresses in one version are offset versions of addresses in the other version, you might do something like:

public class Version
{
    public int Offset { get; init; }

    public int Property1Address => 0xB7CE50 + Offset;
    public int Property2Address => 0xB80000 + Offset;
    public int Property3Address { get; init; }

    public static Version Version1 { get; } = new()
    {
        Offset = 0x2680,            // <-- Offset for version 1
        Property3Address = 123456,  // <-- Explicit address for version 1
    };

    public static Version Version2 { get; } = new()
    {
        Offset = 0,                 // <-- No offset for version 2
        Property3Address = 987654,  // <-- Different explicit address for version 2
    };

    private Version() { }
}

One way to solve this is to introduce an abstract base class that has concrete implementations of all the properties that are the same for both versions, and abstract declarations for the implementations that differ between the two versions.

For example:

public abstract class MyBaseClass
{
    public abstract int PropertyThatDiffersByVersion { get; set; }

    public int PropertyThatIsTheSameForBothVersions
    {
        get
        {
            return Memory.ReadMemory<int>(0xB82677); // I made up the address.
        }

        set
        {
            Memory.WriteMemory<int>(0xB82677, value);
        }
    }
}

public sealed class MyVersion1 : MyBaseClass
{
    public override int PropertyThatDiffersByVersion
    {
        get
        {
            return Memory.ReadMemory<int>(0xB7CE50 + 0x2680);
        }

        set
        {
            Memory.WriteMemory<int>(0xB7CE50 + 0x2680, value);
        }
    }
}

public sealed class MyVersion2 : MyBaseClass
{
    public override int PropertyThatDiffersByVersion
    {
        get
        {
            return Memory.ReadMemory<int>(0xB7CE50);
        }

        set
        {
            Memory.WriteMemory<int>(0xB7CE50, value);
        }
    }
}

Then you would create an instance of the appropriate class (MyVersion1 or MyVersion2), and pass it to methods that accepted an object of type MyBaseClass.

Related