Jason
Jason

Reputation: 1

Using reflection to get all classes that implement a interface and only initialise class that matches ID/opcode given

So in the following code, there is a Instruction interface that is used by a number of classes such as AddInstruction, DivideInstruction and so on. Each of the classes that implements the interface assignes a label and opcode as per the Instruction interface.

Instruction.cs

public abstract class Instruction
    {
        private string label;
        private string opcode;

        protected Instruction(string label, string opcode)
        {
            this.label = label;
            this.opcode = opcode;
        }

        public abstract void Execute(Machine m);
    }

Example of one of the many instructions all of which have the same basic functionalities

AddInstruction.cs

public class AddInstruction : Instruction
    {
        
        private int reg, s1, s2;

        public AddInstruction(string lab, int reg, int s1, int s2) : base(lab, "add") // here the base interface is Instruction and we are assigning the opcode as add, other instructions have there own opcodes //
        {
            this.reg = reg;
            this.s1 = s1;
            this.s2 = s2;
        }

        public override void Execute(Machine m) =>
            // do something
}

From here I want to use the factory pattern along with Reflection to make it so in the future new instructions with there own opcodes can be initiated based on the opcode provided.

InstructionFactoryInterface.cs

interface InstructionFactoryInterface
    {
        Instruction GetInstruction(string opcode);
    }

InstructionFactory.cs

class InstructionFactory : InstructionFactoryInterface
    {
        public Instruction GetInstruction(string opcode)
        {
            Type type = typeof(Instruction);
            var types = AppDomain.CurrentDomain.GetAssemblies()
                .SelectMany(s => s.GetTypes())
                .Where(p => type.IsAssignableFrom(p));
            foreach (var type1 in types)
            {
                // loop through all the types and only return the class that has a matching opcode //
            }
   }

Now here is where i am suck, how can i loop through all the types that implement the Instruction interface and only return the type that matches the opcode parameter passed in. Thank you

Upvotes: 0

Views: 1602

Answers (4)

Sean Skelly
Sean Skelly

Reputation: 1344

How about using attributes? It's very extensible for the future. Though since your opcode is just a string, you'll have to be careful about both string comparison and also uniqueness, as you might end up with two classes sharing the same opcode in the future.

The custom attribute class:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class InstructionAttribute : Attribute
{
    public string OpCode { get; private set; } = string.Empty;
    public InstructionAttribute(string opCode)
    {
        OpCode = opCode;
    }
}

Marking a derived class:

[Instruction("the opCode associated with this type")]
public class AddInstruction : Instruction
{
    // rest of class
}

Changes to InstructionFactory that use the attribute to find the desired type:

class InstructionFactory : InstructionFactoryInterface
{
    public Instruction GetInstruction(string opcode)
    {
        Type type = typeof(Instruction);
        var types = AppDomain.CurrentDomain.GetAssemblies()
            .SelectMany(s => s.GetTypes())
            .Where(p => type.IsAssignableFrom(p));
        foreach (var type1 in types)
        {
            var att = type1.GetCustomAttribute<InstructionAttribute>(false); // don't check ancestors; only this type
            string attributeOpCode = att?.OpCode;
            if (!string.IsNullOrWhiteSpace(attributeOpCode) && attributeOpCode == opcode)
            {
                // construct an instance of type1 however you prefer, return it
            }
        }
        return null; // or other error handling
    }
}

After this, you can add any amount of new classes derived from Instruction - you just have to attribute them with the opcode string you want to use as the identifier.

Upvotes: 0

Olivier Jacot-Descombes
Olivier Jacot-Descombes

Reputation: 112632

It is easier to use a dictionary and to register known operations along with a factory method (or delegate)

public static Dictionary<string, Func<string, int, int, int, Instruction>> FactoryDict
{ get; } = new Dictionary<string, Func<string, int, int, int, Instruction>> {
    ["add"] = (lab, reg, s1, s2) => new AddInstruction(lab, reg, s1, s2),
    ["sub"] = (lab, reg, s1, s2) => new SubInstruction(lab, reg, s1, s2),
    //...
};

Note that you don't have to use all the parameters:

    ["nop"] = (lab, reg, s1, s2) => new NopInstruction(lab, reg),

Then you can get an instruction with

public Instruction GetInstruction(string opcode, string lab, int reg, int s1, int s2)
{
    if (FactoryDict.TryGetValue(opcode, out var create)) {
        return create(lab, reg, s1, s2);
    }
    return null;
}

You can do it with reflection, if you use a naming convention, e.g. name of instruction class is opcode with an upper case first letter + "Instruction":

public Instruction GetInstruction(string opcode, string lab, int reg, int s1, int s2)
{
    // E.g. make "AddInstruction" out of "add"
    string instructionName = $"{Char.ToUpper(opcode[0])}{opcode.Substring(1)}Instruction";

    Type baseType = typeof(Instruction);
    Type instructionType = AppDomain.CurrentDomain.GetAssemblies()
        .SelectMany(s => s.GetTypes())
        .Where(p => baseType.IsAssignableFrom(p) &&
                    !p.IsAbstract &&
                    p.Name == instructionName)
        .FirstOrDefault();
    if (instructionType != null) {
        return (Instruction)Activator.CreateInstance(instructionType, lab, reg, s1, s2);
    }
    return null;
}

Also, having always constructors with a standardised parameter list makes things easier.

Upvotes: 1

Uranus
Uranus

Reputation: 1690

The System.Reflection API does not allow you to obtain IL instructions from the constructor body. Hence, you cannot determine which parameter the constructor of a specific class passes to the base constructor.

You can accomplish this task using the Mono.Cecil library (works with .NET Framework as well).

class InstructionFactory : InstructionFactoryInterface {
    static readonly IDictionary<string, Type> Instructions = new Dictionary<string, Type>();
    static InstructionFactory() {
        InitializeDictionary();
    }
    static void InitializeDictionary() {
        Type type = typeof(Instruction);
        var types = AppDomain.CurrentDomain.GetAssemblies()
            .SelectMany(s => s.GetTypes())
            .Where(p => type.IsAssignableFrom(p));
        foreach(var type1 in types) {
            try {
                string type1Opcode = GetOpcode(type1);
                Instructions.Add(type1Opcode, type1);
            } catch(InvalidOperationException ex) {
                LogError(ex);
            }
        }
    }
    static void LogError(InvalidOperationException ex) {
        // ...
    }
    public Instruction GetInstruction(string opcode, params object[] args) {
        Type instructionType;
        bool found = Instructions.TryGetValue(opcode, out instructionType);
        if(!found) {
            throw new InvalidOperationException($"Instruction {opcode} not found");
        }
        return (Instruction)Activator.CreateInstance(instructionType, args);
    }
    static readonly IDictionary<string, AssemblyDefinition> AssemblyDefinitions = new Dictionary<string, AssemblyDefinition>();
    static AssemblyDefinition GetAssemblyDefinition(Type type) {
        AssemblyDefinition ad;
        bool found = AssemblyDefinitions.TryGetValue(type.Assembly.Location, out ad);
        if(!found) {
            ad = AssemblyDefinition.ReadAssembly(type.Assembly.Location);
            AssemblyDefinitions.Add(type.Assembly.Location, ad);
        }
        return ad;
    }
    static string GetOpcode(Type type) {
        AssemblyDefinition adType = GetAssemblyDefinition(type);
        TypeDefinition tdType = adType.MainModule.GetType(type.FullName);
        IEnumerable<MethodDefinition> ctors = tdType.GetConstructors();
        AssemblyDefinition adInstruction = GetAssemblyDefinition(typeof(Instruction));
        TypeDefinition tdInstruction = adInstruction.MainModule.GetType(typeof(Instruction).FullName);
        MethodDefinition ctorInstruction = tdInstruction.GetConstructors().Single();
        foreach(MethodDefinition ctor in ctors) {
            for(int i = 0; i < ctor.Body.Instructions.Count; i++) {
                Mono.Cecil.Cil.Instruction instr = ctor.Body.Instructions[i];
                if(instr.OpCode.Code == Code.Call && instr.Operand is MethodDefinition md && md == ctorInstruction) {
                    Mono.Cecil.Cil.Instruction lastParameter = ctor.Body.Instructions[i - 1];
                    return (string)lastParameter.Operand;
                }
            }
        }
        throw new InvalidOperationException($"{type.FullName} does not call the base constructor");
    }
}

Upvotes: 0

Jedi_Maseter_Sam
Jedi_Maseter_Sam

Reputation: 813

This is a bit tricky since AddInstruction does not have a default constructor. That being said, it can be done. You will need to use FormatterServices.GetUninitializedObject()to get an instance without using the constructor. The base class Instruction will also need to be modified so opcode is not field, instead it is a method that is implemented in each child class. Here is an example:

using System;
using System.Runtime.Serialization;

namespace Generator
{
    public class Program
    {
        public static void Main()
        {
            var add = FormatterServices.GetUninitializedObject(typeof(AddInstruction));
            var opcode = ((Instruction) add).GetOpCode();
            
            Console.WriteLine(opcode);
        }
    }

    public abstract class Instruction
    {
        public abstract string GetOpCode();
    }

    public class AddInstruction : Instruction
    {
        private int s1, s2;

        public AddInstruction(int s1, int s2)
        {
            this.s1 = s1;
            this.s2 = s2;
        }

        public override string GetOpCode()
        {
            return "add";
        }
    }
} 

Upvotes: 0

Related Questions