using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Collections.Generic;
using MethodBody = Mono.Cecil.Cil.MethodBody;

/// <summary>
/// https://github.com/snaphat/MonoCecilExtensions
/// Provides extension methods for classes from the Mono.Cecil library, a library for reading and writing Intermediate Language (IL) code.
/// These extensions facilitate manipulation of IL code, providing functionality to clone, merge, and update types in collections, methods, fields, and other components of a .NET assembly.
/// </summary>
public static class MonoCecilExtensions
{
    /// <summary>
    /// Represents an information container for updating Mono.Cecil definitions.
    /// </summary>
    public class UpdateInfo
    {
        /// <summary>
        /// A collection of CustomAttribute objects that have been updated.
        /// </summary>
        public readonly Collection<CustomAttribute> updatedAttributes = new Collection<CustomAttribute>();

        /// <summary>
        /// A collection of InterfaceImplementation objects that have been updated.
        /// </summary>
        public readonly Collection<InterfaceImplementation> updatedInterfaces = new Collection<InterfaceImplementation>();

        /// <summary>
        /// A collection of FieldDefinition objects that have been updated.
        /// </summary>
        public readonly Collection<FieldDefinition> updatedFields = new Collection<FieldDefinition>();

        /// <summary>
        /// A collection of PropertyDefinition objects that have been updated.
        /// </summary>
        public readonly Collection<PropertyDefinition> updatedProperties = new Collection<PropertyDefinition>();

        /// <summary>
        /// A collection of MethodDefinition objects that have been updated.
        /// </summary>
        public readonly Collection<MethodDefinition> updatedMethods = new Collection<MethodDefinition>();

        /// <summary>
        /// A collection of source TypeDefinition objects that are being merged.
        /// </summary>
        public readonly Collection<TypeDefinition> srcTypes = new Collection<TypeDefinition>();

        /// <summary>
        /// A collection of destination TypeDefinition objects where source objects are merged into.
        /// </summary>
        public readonly Collection<TypeDefinition> destTypes = new Collection<TypeDefinition>();
    };

    /// <summary>
    /// A dictionary mapping from AssemblyDefinition objects to their corresponding UpdateInfo objects.
    /// Used to keep track of the updates made to each assembly.
    /// </summary>
    public static readonly Dictionary<AssemblyDefinition, UpdateInfo> assemblyUpdateInfo = new Dictionary<AssemblyDefinition, UpdateInfo> ();

    /// <summary>
    /// Additional search directories for resolving assembly types.
    /// </summary>
    public static readonly Collection<string> additionalSearchDirectories = new Collection<string>();

    // Basic extension methods for loading assemblies, adding elements to collections, and finding types, fields, and methods in Mono.Cecil objects.
    #region Base

    /// <summary>
    /// This extension method loads an assembly from a given location.
    /// </summary>
    /// <param name="location">The location of the assembly to be loaded.</param>
    /// <param name="readWrite">A boolean value to determine if the assembly is read-only or writable.</param>
    /// <returns>The AssemblyDefinition object of the loaded assembly if successful</returns>
    public static AssemblyDefinition LoadAssembly(this string location, bool readWrite = false)
    {
        // Create a new instance of the DefaultAssemblyResolver.
        var resolver = new DefaultAssemblyResolver();

        // Add search directories to the resolver.
        foreach (var directory in additionalSearchDirectories)
            resolver.AddSearchDirectory(directory);
        resolver.AddSearchDirectory(Path.GetDirectoryName(typeof(int).Assembly.Location));
        resolver.AddSearchDirectory(Path.Combine(Path.GetDirectoryName(typeof(int).Assembly.Location), "Facades"));

        // Read and return the assembly using the provided location and reader parameters.
        return AssemblyDefinition.ReadAssembly(location, new ReaderParameters()
        {
            AssemblyResolver = resolver,
            ReadWrite = readWrite,
        });
    }

    /// <summary>
    /// This extension method finds a method of a given type in an assembly.
    /// </summary>
    /// <param name="assembly">The assembly where the type and method are located.</param>
    /// <param name="typeSignature">The full or simple name of the type.</param>
    /// <param name="methodSignature">The full or simple name of the method.</param>
    /// <returns>The MethodDefinition object of the found method. Null if not found.</returns>
    public static MethodDefinition FindMethodOfType(this AssemblyDefinition assembly, string typeSignature, string methodSignature)
    {
        // Find and return the method of the given type in the assembly.
        return assembly.FindType(typeSignature)?.FindMethod(methodSignature);
    }

    /// <summary>
    /// This extension method finds a type in an assembly using its full name or simple name.
    /// </summary>
    /// <param name="assembly">The assembly where the type is located.</param>
    /// <param name="typeSignature">The full or simple name of the type.</param>
    /// <returns>The TypeDefinition object of the found type. Null if not found.</returns>
    public static TypeDefinition FindType(this AssemblyDefinition assembly, string typeSignature)
    {
        // Return the first type that matches the provided type signature.
        return assembly.MainModule.Types.FirstOrDefault(type => type.FullName == typeSignature || type.Name == typeSignature);
    }

    /// <summary>
    /// This extension method finds a type in an assembly using its full name or simple name.
    /// </summary>
    /// <param name="assembly">The assembly where the type is located.</param>
    /// <param name="type">The type to locate.</param>
    /// <returns>The TypeDefinition object of the found type. Null if not found.</returns>
    public static TypeDefinition FindType(this AssemblyDefinition assembly, Type type)
    {
        // Return the first type that matches the provided type signature.
        return assembly.MainModule.Types.FirstOrDefault(_type => _type.FullName == type.FullName || _type.Name == type.Name);
    }

    /// <summary>
    /// This extension method finds a field in a type.
    /// </summary>
    /// <param name="type">The type where the field is located.</param>
    /// <param name="fieldSignature">The full or simple name of the field.</param>
    /// <returns>The FieldDefinition object of the found field. Null if not found.</returns>
    public static FieldDefinition FindField(this TypeDefinition type, string fieldSignature)
    {
        // Return the first field that matches the provided field signature.
        return type.Fields.FirstOrDefault(m => m.FullName == fieldSignature || m.Name == fieldSignature);
    }

    /// <summary>
    /// This extension method finds a method in a type.
    /// </summary>
    /// <param name="type">The type where the method is located.</param>
    /// <param name="methodSignature">The full or simple name of the method.</param>
    /// <returns>The MethodDefinition object of the found method. Null if not found.</returns>
    public static MethodDefinition FindMethod(this TypeDefinition type, string methodSignature)
    {
        // The function checks each method in the type's Methods collection,
        // and returns the first method whose full name or simple name matches the provided method signature.
        return type.Methods.FirstOrDefault(m => m.FullName == methodSignature || m.Name == methodSignature);
    }

    /// <summary>
    /// This extension method finds all methods in a type that match a given method signature.
    /// </summary>
    /// <param name="type">The type where the methods are located.</param>
    /// <param name="methodSignature">The full or simple name of the methods.</param>
    /// <returns>A collection of MethodDefinition objects for the found methods. Empty collection if none found.</returns>
    public static Collection<MethodDefinition> FindMethods(this TypeDefinition type, string methodSignature)
    {
        var collection = new Collection<MethodDefinition>();

        // This function checks each method in the type's Methods collection,
        // and adds those methods to the collection whose full name or simple name matches the provided method signature.
        foreach (var item in type.Methods.Where(m => m.FullName == methodSignature || m.Name == methodSignature))
            collection.Add(item);
        return collection;
    }

    #endregion Base

    // Extension method that handles adding types to an assembly.
    #region AddType

    /// <summary>
    /// Adds a type to an assembly. This includes adding the type's fields, properties, and methods.
    /// If the source type is nested, it will be added as a nested type within the parent type in the destination assembly.
    /// </summary>
    /// <param name="assembly">The assembly to which the type will be added.</param>
    /// <param name="src">The source type that will be added to the assembly.</param>
    /// <param name="avoidSignatureConflicts">Avoid name conflicts by adding a '_' suffix to the copied class name.</param>
    public static void AddType(this AssemblyDefinition assembly, TypeDefinition src, bool avoidSignatureConflicts = false)
    {
        // Check for signature conflict avoidance
        var srcName = src.Name;
        if (avoidSignatureConflicts) src.Name += "_";

        // Create a new TypeDefinition with the same properties as the source type
        var dest = new TypeDefinition(src.Namespace, src.Name, src.Attributes);

        // If the source type isn't nested, add the new type directly to the assembly's types
        // Otherwise, find the declaring type in the assembly and add the new type as a nested type
        if (!src.IsNested)
            assembly.MainModule.Types.Add(dest);
        else
            assembly.FindType(src.DeclaringType.FullName).NestedTypes.Add(dest);

        // Set the base type of the new type to match the base type of the source type
        dest.BaseType = src.BaseType;

        // Add the fields, properties, and methods from the source type to the new type
        dest.AddFieldsPropertiesAndMethods(src);

        // Restore name
        if (avoidSignatureConflicts) src.Name = srcName;
    }

    #endregion AddType

    // Extension method that handles the addition of fields, properties, and methods from a source type to a destination type.
    // This is a key part of merging two types, ensuring the destination type includes all necessary components from the source type.
    #region AddFieldsPropertiesAndMethods

    /// <summary>
    /// Merges the source type into the destination type by cloning the fields, properties, and methods of the source, updating their types and adding them to the destination.
    /// </summary>
    /// <param name="dest">The destination type definition where fields, properties, and methods from source will be added.</param>
    /// <param name="src">The source type definition whose fields, properties, and methods will be cloned and added to the destination.</param>
    public static void AddFieldsPropertiesAndMethods(this TypeDefinition dest, TypeDefinition src)
    {
        // Add nested types to the module
        foreach (var subtype in src.NestedTypes)
            dest.Module.Assembly.AddType(subtype);

        // Clone attributes from the source and add to the destination
        var clonedAttributes = new Collection<CustomAttribute>();
        foreach (var attribute in src.CustomAttributes)
        {
            var clonedAttribute = attribute.Clone();
            dest.CustomAttributes.Add(clonedAttribute);
            clonedAttributes.Add(clonedAttribute);
        }

        // Clone interfaces from the source and add to the destination
        var clonedInterfaces = new Collection<InterfaceImplementation>();
        foreach (var @interface in src.Interfaces)
        {
            var clonedInterface = @interface.Clone();
            dest.Interfaces.Add(clonedInterface);
            clonedInterfaces.Add(clonedInterface);
        }

        // Clone fields from the source and add to the destination
        var clonedFields = new Collection<FieldDefinition>();
        foreach (var field in src.Fields)
        {
            var clonedField = field.Clone();
            clonedFields.Add(clonedField);
            dest.Fields.Add(clonedField);
        }

        // Clone properties from the source and add to the destination
        var clonedProperties = new Collection<PropertyDefinition>();
        foreach (var property in src.Properties)
        {
            var clonedProperty = property.Clone();
            clonedProperties.Add(clonedProperty);
            dest.Properties.Add(clonedProperty);
        }

        // Clone methods from the source (don't add to the destination yet)
        var clonedMethods = new Collection<MethodDefinition>();
        foreach (var method in src.Methods)
        {
            var clonedMethod = method.Clone();
            clonedMethods.Add(clonedMethod);
        }

        // List for keeping track of methods that need further processing
        var updatedMethods = new Collection<MethodDefinition>();

        // Process each method
        foreach (var clonedMethod in clonedMethods.ToList())
        {
            // Special handling for constructors
            if (clonedMethod.Name is ".ctor" || clonedMethod.Name is ".cctor" || clonedMethod.Name is "Finalize")
            {
                // Temporarily set the declaring type of the cloned method to the destination type
                // This is required to get the correct full name of the method for the FindMethod call
                clonedMethod.DeclaringType = dest;

                // Find an existing method in the destination type that matches the full name of the cloned method
                // Note that the full name of a method includes the name of its declaring type
                var destMethod = dest.FindMethod(clonedMethod.FullName);

                // Reset the declaring type of the cloned method to null
                // This is done because the cloned method hasn't been added to the destination type yet,
                // and leaving the declaring type set will cause failures to add the method to the destination type
                clonedMethod.DeclaringType = null;

                // If destination already contains a constructor/destructor, merge the instructions
                if (destMethod != null)
                {
                    var clonedInstructions = clonedMethod.Body.Instructions;
                    var trimmedClonedInstructions = clonedInstructions.ToList();

                    // For constructors
                    if (clonedMethod.Name is ".ctor")
                    {
                        // Find the constructor call instruction and remove the instructions before it
                        // This is done to prevent calling the base class constructor twice when merging
                        var callIndex = trimmedClonedInstructions.FindIndex(x => x.OpCode == OpCodes.Call);

                        // Check if callIndex is within valid range
                        if (callIndex < 0 || callIndex >= trimmedClonedInstructions.Count)
                            throw new Exception("Invalid Call Instruction Index in cloned method.");

                        // Remove starting instructions
                        trimmedClonedInstructions.RemoveRange(0, callIndex + 1);
                        trimmedClonedInstructions.RemoveAt(trimmedClonedInstructions.Count - 1);

                        // Insert the trimmed instructions to the existing constructor, just before the last instruction (ret)
                        int insertIndex = destMethod.Body.Instructions.Count - 1;
                        foreach (var clonedInstruction in trimmedClonedInstructions)
                        {
                            destMethod.Body.Instructions.Insert(insertIndex, clonedInstruction);
                            insertIndex++;
                        }
                    }
                    // For static constructors
                    else if (clonedMethod.Name is ".cctor")
                    {
                        // Remove the last instruction (ret)
                        trimmedClonedInstructions.RemoveAt(trimmedClonedInstructions.Count - 1);

                        // Insert the trimmed instructions to the existing static constructor, just before the last instruction (ret)
                        int insertIndex = destMethod.Body.Instructions.Count - 1;
                        foreach (var clonedInstruction in trimmedClonedInstructions)
                        {
                            destMethod.Body.Instructions.Insert(insertIndex, clonedInstruction);
                            insertIndex++;
                        }
                    }
                    // For destructors
                    else if (clonedMethod.Name is "Finalize")
                    {
                        // Find the leave.s instruction and remove the instructions after it.
                        // This is done to prevent calling the base class destructor twice when merging.
                        var trimIndex = trimmedClonedInstructions.FindIndex(x => x.OpCode == OpCodes.Leave_S);

                        // Check if trimIndex is within valid range
                        if (trimIndex < 0 || trimIndex >= trimmedClonedInstructions.Count)
                            throw new Exception("Invalid trim index in cloned method.");

                        // Remove instructions after leave.s (inclusive)
                        trimmedClonedInstructions.RemoveRange(trimIndex, trimmedClonedInstructions.Count - trimIndex);

                        // Insert the trimmed instructions to the existing destructor, at the beginning
                        int insertionIndex = 0;
                        foreach (var clonedInstruction in trimmedClonedInstructions)
                        {
                            destMethod.Body.Instructions.Insert(insertionIndex, clonedInstruction);
                            insertionIndex++;
                        }
                    }

                    // Remove the cloned constructor or destructor from the list of methods to add to the destination type
                    _ = clonedMethods.Remove(clonedMethod);

                    // Add the method to the list of methods to update since it has been modified
                    updatedMethods.Add(destMethod);
                }
                else
                {
                    // Add the cloned constructor to the destination type
                    updatedMethods.Add(clonedMethod);
                }
            }
            else
            {
                // For non-constructor/non-destructor methods
                updatedMethods.Add(clonedMethod);
            }
        }

        // Add updated methods to the destination type
        foreach (var method in clonedMethods) dest.Methods.Add(method);

        // Add updated attributes, interfaces, fields, properties and methods to the update info
        if (!assemblyUpdateInfo.TryGetValue(dest.Module.Assembly, out var updateInfo))
            updateInfo = assemblyUpdateInfo[dest.Module.Assembly] = new UpdateInfo();
        foreach (var attribute in clonedAttributes) updateInfo.updatedAttributes.Add(attribute);
        foreach (var @interface in clonedInterfaces) updateInfo.updatedInterfaces.Add(@interface);
        foreach (var field in clonedFields) updateInfo.updatedFields.Add(field);
        foreach (var property in clonedProperties) updateInfo.updatedProperties.Add(property);
        foreach (var method in updatedMethods) updateInfo.updatedMethods.Add(method);

        // Add source and destination types to the update info
        updateInfo.srcTypes.Add(src);
        updateInfo.destTypes.Add(dest);
    }

    #endregion AddFieldsPropertiesAndMethods

    // Extension methods that handle the updating of fields, properties, and methods within a destination type after they have been cloned from a source type.
    // These methods ensure that the newly added components in the destination type correctly reference the destination type, rather than the original source type.
    #region UpdateFieldsPropertiesAndMethods

    /// <summary>
    /// Updates the types of attributes, interfaces, fields, properties, and methods within a given assembly.
    /// This includes updating the types in interfaces, fields, properties, and methods. It also updates the getter and setter methods for properties,
    /// updates the instruction types for methods, imports references for attributes, interfaces, fields, properties, and methods,
    /// imports base types of each destination type, and swaps any duplicate methods in the destination types.
    /// </summary>
    /// <param name="assembly">The assembly to be updated. This assembly's types are matched against the source types and replaced with the corresponding destination types, based on previously registered update information.</param>
    /// <param name="avoidSignatureConflicts">Avoid signature conflicts by changing original method parameters to be base object types for duplicate methods</param>
    public static void UpdateFieldsPropertiesAndMethods(this AssemblyDefinition assembly, bool avoidSignatureConflicts = false)
    {
        // Check if update information exists for the assembly
        if (assemblyUpdateInfo.TryGetValue(assembly, out var updateInfo))
        {
            // Update types in interfaces, fields, properties, and methods
            for (int i = 0; i < updateInfo.destTypes.Count; ++i)
                foreach (var @interface in updateInfo.updatedInterfaces) @interface.UpdateTypes(updateInfo.srcTypes[i], updateInfo.destTypes[i]);
            for (int i = 0; i < updateInfo.destTypes.Count; ++i)
                foreach (var field in updateInfo.updatedFields) field.UpdateTypes(updateInfo.srcTypes[i], updateInfo.destTypes[i]);
            for (int i = 0; i < updateInfo.destTypes.Count; ++i)
                foreach (var property in updateInfo.updatedProperties) property.UpdateTypes(updateInfo.srcTypes[i], updateInfo.destTypes[i]);
            for (int i = 0; i < updateInfo.destTypes.Count; ++i)
                foreach (var method in updateInfo.updatedMethods) method.UpdateTypes(updateInfo.srcTypes[i], updateInfo.destTypes[i]);

            // Update getter and setter methods for properties
            for (int i = 0; i < updateInfo.destTypes.Count; ++i)
                foreach (var property in updateInfo.updatedProperties) property.UpdateGettersAndSetters(updateInfo.srcTypes[i], updateInfo.destTypes[i]);

            // Update instruction types for methods
            for (int i = 0; i < updateInfo.destTypes.Count; ++i)
                foreach (var method in updateInfo.updatedMethods) method.UpdateInstructionTypes(updateInfo.srcTypes[i], updateInfo.destTypes[i]);

            // Check for optimization opportunities for methods
            foreach (var method in updateInfo.updatedMethods) method.OptimizeInstructions();

            // Import references for attributes, interfaces, fields, properties, and methods
            foreach (var attribute in updateInfo.updatedAttributes) attribute.ImportReferences(assembly.MainModule);
            foreach (var @interface in updateInfo.updatedInterfaces) @interface.ImportReferences(assembly.MainModule);
            foreach (var field in updateInfo.updatedFields) field.ImportReferences(assembly.MainModule);
            foreach (var property in updateInfo.updatedProperties) property.ImportReferences(assembly.MainModule);
            foreach (var method in updateInfo.updatedMethods) method.ImportReferences(assembly.MainModule);

            // Import base type of each dest type
            foreach (var type in updateInfo.destTypes) type.BaseType = assembly.MainModule.ImportReference(type.BaseType);

            // Swap any duplicate methods in the destination types
            foreach (var type in updateInfo.destTypes) type.SwapDuplicateMethods(avoidSignatureConflicts);

            // Remove the assembly from the update information collection
            _ = assemblyUpdateInfo.Remove(assembly);
        }
    }

    #endregion UpdateFieldsPropertiesAndMethods

    // Extension methods for cloning various Mono.Cecil objects.
    #region Clone

    /// <summary>
    /// Clones a CustomAttribute.
    /// </summary>
    /// <param name="attribute">The attribute to be cloned.</param>
    /// <returns>A clone of the original attribute.</returns>
    public static CustomAttribute Clone(this CustomAttribute attribute)
    {
        // Create a new CustomAttribute with the constructor of the original attribute.
        var clonedAttribute = new CustomAttribute(attribute.Constructor);

        // Add all constructor arguments from the original attribute to the cloned attribute.
        foreach (var argument in attribute.ConstructorArguments) clonedAttribute.ConstructorArguments.Add(argument);

        // Add all properties from the original attribute to the cloned attribute.
        foreach (var property in attribute.Properties) clonedAttribute.Properties.Add(property);

        // Add all fields from the original attribute to the cloned attribute.
        foreach (var field in attribute.Fields) clonedAttribute.Fields.Add(field);

        // Return the cloned attribute.
        return clonedAttribute;
    }

    /// <summary>
    /// Clones a InterfaceImplementation.
    /// </summary>
    /// <param name="interface">The interface to be cloned.</param>
    /// <returns>A clone of the original interface.</returns>
    public static InterfaceImplementation Clone(this InterfaceImplementation @interface)
    {
        // Create a new InterfaceImplementation with the type the original interface.
        var clonedInterface = new InterfaceImplementation(@interface.InterfaceType);

        // Copy all custom attributes from the original interface to the cloned interface.
        foreach (var attribute in @interface.CustomAttributes) clonedInterface.CustomAttributes.Add(attribute.Clone());

        // Return the cloned interface.
        return clonedInterface;
    }

    /// <summary>
    /// Clones a FieldDefinition.
    /// </summary>
    /// <param name="field">The field to be cloned.</param>
    /// <returns>A clone of the original field.</returns>
    public static FieldDefinition Clone(this FieldDefinition field)
    {
        // Create a new FieldDefinition with the same properties as the original field.
        var clonedField = new FieldDefinition(field.Name, field.Attributes, field.FieldType);

        // Copy all custom attributes from the original field to the cloned field.
        foreach (var attribute in field.CustomAttributes) clonedField.CustomAttributes.Add(attribute.Clone());

        // Copy the MarshalInfo if it exists.
        clonedField.MarshalInfo = field.MarshalInfo != null ? new MarshalInfo(field.MarshalInfo.NativeType) : null;

        // Copy the initial value of the field.
        clonedField.InitialValue = field.InitialValue;

        // Return the cloned field.
        return clonedField;
    }

    /// <summary>
    /// Clones a PropertyDefinition.
    /// </summary>
    /// <param name="property">The property to be cloned.</param>
    /// <returns>A clone of the original property.</returns>
    public static PropertyDefinition Clone(this PropertyDefinition property)
    {
        // Create a new PropertyDefinition with the same properties as the original property.
        var clonedProperty = new PropertyDefinition(property.Name, property.Attributes, property.PropertyType);

        // Copy all custom attributes from the original property to the cloned property.
        foreach (var attribute in property.CustomAttributes) clonedProperty.CustomAttributes.Add(attribute.Clone());

        // Clone the get and set methods if they exist.
        clonedProperty.GetMethod = property.GetMethod?.Clone();
        clonedProperty.SetMethod = property.SetMethod?.Clone();

        // Return the cloned property.
        return clonedProperty;
    }

    /// <summary>
    /// Clones a ParameterDefinition.
    /// </summary>
    /// <param name="parameter">The parameter to be cloned.</param>
    /// <returns>A clone of the original parameter.</returns>
    public static ParameterDefinition Clone(this ParameterDefinition parameter)
    {
        // Create a new ParameterDefinition with the same properties as the original parameter.
        var clonedParameter = new ParameterDefinition(parameter.Name, parameter.Attributes, parameter.ParameterType);

        // Copy all custom attributes from the original parameter to the cloned parameter.
        foreach (var attribute in parameter.CustomAttributes) clonedParameter.CustomAttributes.Add(attribute.Clone());

        // Return the cloned parameter.
        return clonedParameter;
    }

    /// <summary>
    /// Clones a VariableDefinition.
    /// </summary>
    /// <param name="variable">The variable to be cloned.</param>
    /// <returns>A clone of the original variable.</returns>
    public static VariableDefinition Clone(this VariableDefinition variable)
    {
        // Create and return a new VariableDefinition with the same type as the original variable.
        return new VariableDefinition(variable.VariableType);
    }

    /// <summary>
    /// Clones an Instruction.
    /// </summary>
    /// <param name="instruction">The instruction to be cloned.</param>
    /// <returns>A clone of the original instruction.</returns>
    public static Instruction Clone(this Instruction instruction)
    {
        // Create a new Instruction with a default opcode.
        var clonedInstruction = Instruction.Create(OpCodes.Nop);

        // Copy the opcode and operand from the original instruction to the cloned instruction.
        clonedInstruction.OpCode = instruction.OpCode;
        clonedInstruction.Operand = instruction.Operand;

        // Return the cloned instruction.
        return clonedInstruction;
    }

    /// <summary>
    /// Clones a MethodDefinition.
    /// </summary>
    /// <param name="method">The method to be cloned.</param>
    /// <returns>A clone of the original method.</returns>
    public static MethodDefinition Clone(this MethodDefinition method)
    {
        // Create a new MethodDefinition with the same properties as the original method.
        var clonedMethod = new MethodDefinition(method.Name, method.Attributes, method.ReturnType)
        {
            ImplAttributes = method.ImplAttributes,
            SemanticsAttributes = method.SemanticsAttributes
        };

        // Add all overides from the original method to the cloned method (references).
        foreach (var @override in method.Overrides) clonedMethod.Overrides.Add(@override);

        // Copy all custom attributes from the original method to the cloned method.
        foreach (var attribute in method.CustomAttributes) clonedMethod.CustomAttributes.Add(attribute.Clone());

        // Clone all parameters and add them to the cloned method.
        foreach (var parameter in method.Parameters) clonedMethod.Parameters.Add(parameter.Clone());

        // Create a new method body for the cloned method.
        clonedMethod.Body = new MethodBody(clonedMethod);

        // If the original method has a body, copy the relevant properties to the cloned method's body.
        if (method.HasBody)
        {
            clonedMethod.Body.MaxStackSize = method.Body.MaxStackSize;
            clonedMethod.Body.InitLocals = method.Body.InitLocals;

            // Clone all variables and add them to the cloned method's body.
            foreach (var variable in method.Body.Variables) clonedMethod.Body.Variables.Add(variable.Clone());

            // Instruction mapping from old to new instructions used to update branch targets which is necessary after cloning
            var instructionMapping = new Dictionary<Instruction, Instruction>();

            // Clone all the instructions and create the mapping.
            foreach (var instruction in method.Body.Instructions)
            {
                var clonedInstruction = instruction.Clone();
                instructionMapping[instruction] = clonedInstruction;
                clonedMethod.Body.Instructions.Add(clonedInstruction);
            }

            // Now fix up the branch targets.
            foreach (var instruction in clonedMethod.Body.Instructions)
            {
                // If the instruction is a branch instruction, fix up its target.
                if (instruction.OpCode.FlowControl == FlowControl.Branch ||
                    instruction.OpCode.FlowControl == FlowControl.Cond_Branch)
                {
                    instruction.Operand = instructionMapping[(Instruction)instruction.Operand];
                }

                // If the instruction is a switch instruction, fix up its targets.
                if (instruction.OpCode == OpCodes.Switch)
                {
                    var oldTargets = (Instruction[])instruction.Operand;
                    var newTargets = new Instruction[oldTargets.Length];
                    for (int i = 0; i < oldTargets.Length; ++i)
                    {
                        newTargets[i] = instructionMapping[oldTargets[i]];
                    }
                    instruction.Operand = newTargets;
                }
            }
        }

        // Return the cloned method.
        return clonedMethod;
    }

    public static MethodDefinition CloneToModuleAsStatic(this MethodDefinition method, TypeReference originalType, ModuleDefinition module)
    {
        // Create a new MethodDefinition with the same properties as the original method.
        var clonedMethod = new MethodDefinition(method.Name, MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.HideBySig, module.ImportReference(method.ReturnType))
        {
            ImplAttributes = method.ImplAttributes,
            SemanticsAttributes = method.SemanticsAttributes
        };

        // Copy all custom attributes from the original method to the cloned method.
        foreach (var attribute in method.CustomAttributes)
            clonedMethod.CustomAttributes.Add(attribute.CloneToModule(module));

        if (!method.Attributes.HasFlag(MethodAttributes.Static))
            clonedMethod.Parameters.Add(new ParameterDefinition("self", ParameterAttributes.None, originalType));
        // Clone all parameters and add them to the cloned method.
        foreach (var parameter in method.Parameters)
            clonedMethod.Parameters.Add(parameter.CloneToModule(module));

        // Create a new method body for the cloned method.
        clonedMethod.Body = new MethodBody(clonedMethod);

        // If the original method has a body, copy the relevant properties to the cloned method's body.
        if (method.HasBody)
        {
            clonedMethod.Body.MaxStackSize = method.Body.MaxStackSize;
            clonedMethod.Body.InitLocals = method.Body.InitLocals;

            // Clone all variables and add them to the cloned method's body.
            foreach (var variable in method.Body.Variables) clonedMethod.Body.Variables.Add(variable.CloneToModule(module));

            // Instruction mapping from old to new instructions used to update branch targets which is necessary after cloning
            var instructionMapping = new Dictionary<Instruction, Instruction>();

            // Clone all the instructions and create the mapping.
            foreach (var instruction in method.Body.Instructions)
            {
                var clonedInstruction = instruction.Clone();
                clonedInstruction.Resolve(clonedMethod, method, module);
                instructionMapping[instruction] = clonedInstruction;
                clonedMethod.Body.Instructions.Add(clonedInstruction);
            }

            // Now fix up the branch targets.
            foreach (var instruction in clonedMethod.Body.Instructions)
            {
                // If the instruction is a branch instruction, fix up its target.
                if (instruction.OpCode.FlowControl == FlowControl.Branch ||
                    instruction.OpCode.FlowControl == FlowControl.Cond_Branch)
                {
                    instruction.Operand = instructionMapping[(Instruction)instruction.Operand];
                }

                // If the instruction is a switch instruction, fix up its targets.
                if (instruction.OpCode == OpCodes.Switch)
                {
                    var oldTargets = (Instruction[])instruction.Operand;
                    var newTargets = new Instruction[oldTargets.Length];
                    for (int i = 0; i < oldTargets.Length; ++i)
                    {
                        newTargets[i] = instructionMapping[oldTargets[i]];
                    }
                    instruction.Operand = newTargets;
                }
            }

            // copy the exception handler blocks
            foreach (ExceptionHandler eh in method.Body.ExceptionHandlers)
            {
                ExceptionHandler neh = new ExceptionHandler(eh.HandlerType);
                neh.CatchType = module.ImportReference(eh.CatchType);

                // we need to setup neh.Start and End; these are instructions; we need to locate it in the source by index
                if (eh.TryStart != null)
                {
                    int idx = method.Body.Instructions.IndexOf(eh.TryStart);
                    neh.TryStart = clonedMethod.Body.Instructions[idx];
                }
                if (eh.TryEnd != null)
                {
                    int idx = method.Body.Instructions.IndexOf(eh.TryEnd);
                    neh.TryEnd = clonedMethod.Body.Instructions[idx];
                }

                clonedMethod.Body.ExceptionHandlers.Add(neh);
            }
        }

        // Return the cloned method.
        return clonedMethod;
    }

    public static MethodDefinition CloneToModuleAsStatic(this MethodDefinition method, MethodBody body, TypeReference originalType, ModuleDefinition module)
    {
        // Create a new MethodDefinition with the same properties as the original method.
        var clonedMethod = new MethodDefinition(method.Name, MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.HideBySig, module.ImportReference(method.ReturnType))
        {
            ImplAttributes = method.ImplAttributes,
            SemanticsAttributes = method.SemanticsAttributes
        };

        // Copy all custom attributes from the original method to the cloned method.
        foreach (var attribute in method.CustomAttributes)
            clonedMethod.CustomAttributes.Add(attribute.CloneToModule(module));

        if (!method.Attributes.HasFlag(MethodAttributes.Static))
            clonedMethod.Parameters.Add(new ParameterDefinition("self", ParameterAttributes.None, originalType));
        // Clone all parameters and add them to the cloned method.
        foreach (var parameter in method.Parameters)
            clonedMethod.Parameters.Add(parameter.CloneToModule(module));

        // Create a new method body for the cloned method.
        clonedMethod.Body = new MethodBody(clonedMethod);

        // If the original method has a body, copy the relevant properties to the cloned method's body.
        clonedMethod.Body.MaxStackSize = body.MaxStackSize;
        clonedMethod.Body.InitLocals = body.InitLocals;

        // Clone all variables and add them to the cloned method's body.
        foreach (var variable in body.Variables) clonedMethod.Body.Variables.Add(variable.CloneToModule(module));

        // Instruction mapping from old to new instructions used to update branch targets which is necessary after cloning
        var instructionMapping = new Dictionary<Instruction, Instruction>();

        // Clone all the instructions and create the mapping.
        foreach (var instruction in body.Instructions)
        {
            var clonedInstruction = instruction.Clone();
            clonedInstruction.Resolve(clonedMethod, method, module);
            instructionMapping[instruction] = clonedInstruction;
            clonedMethod.Body.Instructions.Add(clonedInstruction);
        }

        // Now fix up the branch targets.
        foreach (var instruction in clonedMethod.Body.Instructions)
        {
            // If the instruction is a branch instruction, fix up its target.
            if (instruction.OpCode.FlowControl == FlowControl.Branch ||
                instruction.OpCode.FlowControl == FlowControl.Cond_Branch)
            {
                instruction.Operand = instructionMapping[(Instruction)instruction.Operand];
            }

            // If the instruction is a switch instruction, fix up its targets.
            if (instruction.OpCode == OpCodes.Switch)
            {
                var oldTargets = (Instruction[])instruction.Operand;
                var newTargets = new Instruction[oldTargets.Length];
                for (int i = 0; i < oldTargets.Length; ++i)
                {
                    newTargets[i] = instructionMapping[oldTargets[i]];
                }
                instruction.Operand = newTargets;
            }
        }

        // copy the exception handler blocks
        foreach (ExceptionHandler eh in body.ExceptionHandlers)
        {
            ExceptionHandler neh = new ExceptionHandler(eh.HandlerType);
            neh.CatchType = module.ImportReference(eh.CatchType);

            // we need to setup neh.Start and End; these are instructions; we need to locate it in the source by index
            if (eh.TryStart != null)
            {
                int idx = body.Instructions.IndexOf(eh.TryStart);
                neh.TryStart = clonedMethod.Body.Instructions[idx];
            }
            if (eh.TryEnd != null)
            {
                int idx = body.Instructions.IndexOf(eh.TryEnd);
                neh.TryEnd = clonedMethod.Body.Instructions[idx];
            }

            clonedMethod.Body.ExceptionHandlers.Add(neh);
        }

        // Return the cloned method.
        return clonedMethod;
    }

    public static VariableDefinition CloneToModule(this VariableDefinition variable, ModuleDefinition module)
    {
        // Create and return a new VariableDefinition with the same type as the original variable.
        return new VariableDefinition(module.ImportReference(variable.VariableType));
    }

    public static ParameterDefinition CloneToModule(this ParameterDefinition parameter, ModuleDefinition module)
    {
        // Create a new ParameterDefinition with the same properties as the original parameter.
        var clonedParameter = new ParameterDefinition(parameter.Name, parameter.Attributes, module.ImportReference(parameter.ParameterType));

        // Copy all custom attributes from the original parameter to the cloned parameter.
        foreach (var attribute in parameter.CustomAttributes)
        {
            module.ImportReference(attribute.GetType());
            clonedParameter.CustomAttributes.Add(attribute.CloneToModule(module));
        }

        // Return the cloned parameter.
        return clonedParameter;
    }

    public static CustomAttribute CloneToModule(this CustomAttribute attribute, ModuleDefinition module)
    {
        // Create a new CustomAttribute with the constructor of the original attribute.
        var clonedAttribute = new CustomAttribute(module.ImportReference(attribute.Constructor));

        // Add all constructor arguments from the original attribute to the cloned attribute.
        foreach (var argument in attribute.ConstructorArguments)
        {
            module.ImportReference(argument.Type);
            if (argument.Value != null)
            {
                module.ImportReference(argument.Value?.GetType());
            }
            clonedAttribute.ConstructorArguments.Add(argument);
        }

        // Add all properties from the original attribute to the cloned attribute.
        foreach (var property in attribute.Properties)
        {
            module.ImportReference(property.Argument.Type);
            if (property.Argument.Value != null)
            {
                module.ImportReference(property.Argument.Value?.GetType());
            }
            clonedAttribute.Properties.Add(property);
        }
        // Add all fields from the original attribute to the cloned attribute.
        foreach (var field in attribute.Fields)
        {
            module.ImportReference(field.Argument.Type);
            if (field.Argument.Value != null)
            {
                module.ImportReference(field.Argument.Value?.GetType());
            }
            clonedAttribute.Fields.Add(field);
        }

        // Return the cloned attribute.
        return clonedAttribute;
    }

    private static void Resolve(this Instruction instruction, MethodDefinition method_new, MethodDefinition method_original, ModuleDefinition module)
    {
        var operand = instruction.Operand;
        if (operand == null)
            return;

        if (operand is MethodDefinition opmtddef)
        {
            if (opmtddef.FullName.Equals(method_original.FullName))
                instruction.Operand = method_new;
            else
                instruction.Operand = module.ImportReference(opmtddef);
            return;
        }

        if (operand is MethodReference opmtdref)
        {
            instruction.Operand = module.ImportReference(opmtdref);
            return;
        }

        if (operand is FieldReference opfldref)
        {
            instruction.Operand = module.ImportReference(opfldref);
            return;
        }

        if (operand is TypeReference optyperef)
        {
            instruction.Operand = module.ImportReference(optyperef);
            return;
        }
    }

    #endregion Clone

    // Extension methods for replacing references to a source type with references to a destination type within Mono.Cecil objects.
    // This is used to ensure that copied fields, properties, and methods reference copied types instead of the originals.
    #region UpdateTypes

    /// <summary>
    /// Updates the InterfaceType of the given InterfaceImplementation, if it matches the source type, to the destination type.
    /// </summary>
    /// <param name="interface">InterfaceImplementation that may have its InterfaceType updated.</param>
    /// <param name="src">The source type which could be replaced.</param>
    /// <param name="dest">The destination type which could replace the source type.</param>
    public static void UpdateTypes(this InterfaceImplementation @interface, TypeDefinition src, TypeDefinition dest)
    {
        // If the interface's type matches the source type, update it to the destination type
        if (@interface.InterfaceType == src) @interface.InterfaceType = dest;
    }

    /// <summary>
    /// Updates the FieldType of the given FieldDefinition, if it matches the source type, to the destination type.
    /// </summary>
    /// <param name="field">FieldDefinition that may have its FieldType updated.</param>
    /// <param name="src">The source type which could be replaced.</param>
    /// <param name="dest">The destination type which could replace the source type.</param>
    public static void UpdateTypes(this FieldDefinition field, TypeDefinition src, TypeDefinition dest)
    {
        // If the field's type matches the source type, update it to the destination type
        if (field.FieldType == src) field.FieldType = dest;
    }

    /// <summary>
    /// Updates the FieldReference and DeclaringType of the given FieldReference, if they match the source type, to the destination type.
    /// If a matching field definition is found in the destination type, a reference to it is returned.
    /// Otherwise, the original field reference is returned.
    /// </summary>
    /// <param name="field">FieldReference that may have its FieldType, and DeclaringType updated.</param>
    /// <param name="src">The source type which could be replaced.</param>
    /// <param name="dest">The destination type which could replace the source type.</param>
    /// <returns>A FieldReference with updated types, or the original FieldReference if no updates were made.</returns>
    public static FieldReference UpdateTypes(this FieldReference field, TypeDefinition src, TypeDefinition dest)
    {
        // Check if the field's FieldType or DeclaringType matches the source type, and if so, replace them with the destination type
        if (field.FieldType == src) field.FieldType = dest;
        if (field.DeclaringType == src) field.DeclaringType = dest;

        // Attempt to find a field in the destination type that matches the field's full name
        // If a matching definition is found, return a reference to it otherwise return original reference
        return dest.FindField(field.FullName) ?? field;
    }

    /// <summary>
    /// Updates the PropertyType of the given PropertyDefinition, if it matches the source type, to the destination type.
    /// </summary>
    /// <param name="property">PropertyDefinition that may have its PropertyType updated.</param>
    /// <param name="src">The source type which could be replaced.</param>
    /// <param name="dest">The destination type which could replace the source type.</param>
    public static void UpdateTypes(this PropertyDefinition property, TypeDefinition src, TypeDefinition dest)
    {
        // If the property's type matches the source type, update it to the destination type
        if (property.PropertyType == src) property.PropertyType = dest;
    }

    /// <summary>
    /// Updates the ParameterType of the given ParameterDefinition, if it matches the source type, to the destination type.
    /// </summary>
    /// <param name="parameter">ParameterDefinition that may have its ParameterType updated.</param>
    /// <param name="src">The source type which could be replaced.</param>
    /// <param name="dest">The destination type which could replace the source type.</param>
    public static void UpdateTypes(this ParameterDefinition parameter, TypeDefinition src, TypeDefinition dest)
    {
        // If the parameter's type matches the source type, update it to the destination type
        if (parameter.ParameterType == src) parameter.ParameterType = dest;
    }

    /// <summary>
    /// Updates the VariableType of the given VariableDefinition, if it matches the source type, to the destination type.
    /// </summary>
    /// <param name="variable">VariableDefinition that may have its VariableType updated.</param>
    /// <param name="src">The source type which could be replaced.</param>
    /// <param name="dest">The destination type which could replace the source type.</param>
    public static void UpdateTypes(this VariableDefinition variable, TypeDefinition src, TypeDefinition dest)
    {
        // If the variable's type matches the source type, update it to the destination type
        if (variable.VariableType == src) variable.VariableType = dest;
    }

    /// <summary>
    /// Updates the ReturnType of the given MethodDefinition, if it matches the source type, to the destination type.
    /// Also updates ParameterTypes and VariableTypes of the MethodDefinition using the same rule.
    /// </summary>
    /// <param name="method">MethodDefinition that may have its ReturnType, ParameterTypes, and VariableTypes updated.</param>
    /// <param name="src">The source type which could be replaced.</param>
    /// <param name="dest">The destination type which could replace the source type.</param>
    public static void UpdateTypes(this MethodDefinition method, TypeDefinition src, TypeDefinition dest)
    {
        // Update method overrides if they match the source type
        for (int i = 0; i < method.Overrides.Count; i++) method.Overrides[i] = method.Overrides[i].UpdateTypes(src, dest);

        // If the method's return type matches the source type, update it to the destination type
        if (method.ReturnType == src) method.ReturnType = dest;

        // Update method parameters and variables if they match the source type
        foreach (var parameter in method.Parameters) parameter.UpdateTypes(src, dest);
        if (method.HasBody) foreach (var variable in method.Body.Variables) variable.UpdateTypes(src, dest);
    }

    /// <summary>
    /// Updates the ReturnType and DeclaringType of the given MethodReference, if they match the source type, to the destination type.
    /// Also updates the ParameterTypes of the MethodReference using the same rule.
    /// If a matching method definition is found in the destination type, a reference to it is returned.
    /// Otherwise, the original method reference is returned.
    /// </summary>
    /// <param name="method">MethodReference that may have its ReturnType, DeclaringType and ParameterTypes updated.</param>
    /// <param name="src">The source type which could be replaced.</param>
    /// <param name="dest">The destination type which could replace the source type.</param>
    /// <returns>A MethodReference with updated types, or the original MethodReference if no updates were made.</returns>
    public static MethodReference UpdateTypes(this MethodReference method, TypeDefinition src, TypeDefinition dest)
    {
        // Update method parameters to destination type
        foreach (var parameter in method.Parameters) parameter.UpdateTypes(src, dest);

        // Check if the method's ReturnType or DeclaringType matches the source type, and if so, replace them with the destination type
        if (method.ReturnType == src) method.ReturnType = dest;
        if (method.DeclaringType == src) method.DeclaringType = dest;

        // Attempt to find a method in the destination type that matches the method's full name
        // If a matching definition is found, return a reference to it otherwise return original reference
        return dest.FindMethod(method.FullName) ?? method;
    }

    /// <summary>
    /// Updates the ReturnType and Parameters of the CallSite to the destination type, if they match the source type, to the destination type.
    /// </summary>
    /// <param name="callSite">CallSite that needs its return type and parameters updated.</param>
    /// <param name="src">The original type which is being replaced.</param>
    /// <param name="dest">The new type which is replacing the original type.</param>
    public static void UpdateTypes(this CallSite callSite, TypeDefinition src, TypeDefinition dest)
    {
        // Update callsite parameters to destination type
        foreach (var parameter in callSite.Parameters) parameter.UpdateTypes(src, dest);

        // If the current return type is the source type, update it to destination type
        if (callSite.ReturnType == src) callSite.ReturnType = dest;
    }

    #endregion UpdateTypes

    // Extension methods for replacing references to a source type with references to a destination type within Mono.Cecil.Instruction objects.
    // This is crucial for ensuring that the instructions within methods correctly reference the fields, properties, and methods of the destination type after cloning from the source type.
    #region UpdateInstructionTypes

    /// <summary>
    /// Updates the Operand of an instruction when merging classes.
    /// The update strategy depends on the type of the operand.
    /// If the operand is a ParameterDefinition, VariableDefinition, FieldReference, MethodReference, CallSite, or TypeReference, it's updated accordingly.
    /// </summary>
    /// <param name="instruction">Instruction that needs its operand updated.</param>
    /// <param name="src">The original type which is being replaced.</param>
    /// <param name="dest">The new type which is replacing the original type.</param>
    public static void UpdateInstructionTypes(this Instruction instruction, TypeDefinition src, TypeDefinition dest)
    {
        // Check operand type and update accordingly
        if (instruction.Operand is ParameterDefinition parameter)
            parameter.UpdateTypes(src, dest);  // Update types in ParameterDefinition
        else if (instruction.Operand is VariableDefinition variable)
            variable.UpdateTypes(src, dest);  // Update types in VariableDefinition
        else if (instruction.Operand is TypeReference type && type == src)
            instruction.Operand = dest;  // Update type in TypeReference
        else if (instruction.Operand is FieldReference field)
            instruction.Operand = field.UpdateTypes(src, dest);  // Update types in FieldReference
        else if (instruction.Operand is MethodReference method)
            instruction.Operand = method.UpdateTypes(src, dest);  // Update types in MethodReference
        else if (instruction.Operand is CallSite callSite)
            callSite.UpdateTypes(src, dest);  // Update types in CallSite
    }

    /// <summary>
    /// Updates all instructions in the method's body.
    /// </summary>
    /// <param name="method">Method whose instructions are to be updated.</param>
    /// <param name="src">The original type which is being replaced.</param>
    /// <param name="dest">The new type which is replacing the original type.</param>
    public static void UpdateInstructionTypes(this MethodDefinition method, TypeDefinition src, TypeDefinition dest)
    {
        // Update instructions in the method body to the destination type
        if (method.HasBody) foreach (var instruction in method.Body.Instructions) UpdateInstructionTypes(instruction, src, dest);
    }

    #endregion UpdateInstructionTypes

    // Extension methods for replacing references to a source type with references to a destination type within Mono.Cecil.Property getter and setter methods.
    // This ensures that the properties of the destination type reference copied getters and setters instead of the originals.
    #region UpdateGettersAndSetters

    /// <summary>
    /// Updates the getter and setter methods of a property to reference the destination type when merging classes.
    /// This method does the following:
    ///     - Clones the existing getter/setter methods, so that any modifications do not affect the original methods
    ///     - Calls UpdateTypes to update all type references within the methods' bodies from src to dest
    ///     - Updates the declaring type of the methods to be dest
    ///     - Finds the equivalent methods in dest (if they exist), and updates the property's getter/setter methods to reference them
    /// This ensures that the property correctly interacts with the destination type after merging.
    /// </summary>
    /// <param name="property">PropertyDefinition whose getter and setter need to be updated.</param>
    /// <param name="src">The original type which is being replaced.</param>
    /// <param name="dest">The new type which is replacing the original type.</param>
    public static void UpdateGettersAndSetters(this PropertyDefinition property, TypeDefinition src, TypeDefinition dest)
    {
        // If the declaring type of the property is the destination type
        if (property.DeclaringType == dest)
        {
            // If the property has a getter, clone and update it
            if (property.GetMethod != null)
            {
                // Clone the getter
                var clonedGetter = property.GetMethod.Clone();
                // Update all type references within the getter from src to dest
                clonedGetter.UpdateTypes(src, dest);
                // Update the declaring type of the getter to be dest
                clonedGetter.DeclaringType = dest;
                // If an equivalent method exists in dest, update the property's getter to reference it
                if (dest.FindMethod(clonedGetter.FullName) is MethodDefinition getMethod)
                    property.GetMethod = getMethod;
            }
            // If the property has a setter, clone and update it
            if (property.SetMethod != null)
            {
                // Clone the setter
                var clonedSetter = property.SetMethod.Clone();
                // Update all type references within the setter from src to dest
                clonedSetter.UpdateTypes(src, dest);
                // Update the declaring type of the setter to be dest
                clonedSetter.DeclaringType = dest;
                // If an equivalent method exists in dest, update the property's setter to reference it
                if (dest.FindMethod(clonedSetter.FullName) is MethodDefinition setMethod)
                    property.SetMethod = setMethod;
            }
        }
    }

    #endregion UpdateGettersAndSetters

    // Extension methods to import references from one module to another.
    // This is important when merging assemblies classes as it allows the destination to access types that may not have been referenced prior.
    #region ImportReferences

    /// <summary>
    /// Imports the constructor reference for a given attribute into a module.
    /// </summary>
    /// <param name="attribute">The custom attribute whose constructor reference needs to be imported.</param>
    /// <param name="module">The module type into whose module the reference should be imported.</param>
    public static void ImportReferences(this CustomAttribute attribute, ModuleDefinition module)
    {
        // Import the constructor reference into the module
        attribute.Constructor = module.ImportReference(attribute.Constructor);
    }

    /// <summary>
    /// Imports the interface type and custom attributes references of an interface into a module.
    /// </summary>
    /// <param name="interface">The interface whose references need to be imported.</param>
    /// <param name="module">The module type into whose module the references should be imported.</param>
    public static void ImportReferences(this InterfaceImplementation @interface, ModuleDefinition module)
    {
        // Import the custom attributes references into the module
        foreach (var attribute in @interface.CustomAttributes) attribute.ImportReferences(module);

        // Import the interface type reference into the module
        @interface.InterfaceType = module.ImportReference(@interface.InterfaceType);
    }

    /// <summary>
    /// Imports the field type and custom attributes references of a field into a module.
    /// </summary>
    /// <param name="field">The field whose references need to be imported.</param>
    /// <param name="module">The module type into whose module the references should be imported.</param>
    public static void ImportReferences(this FieldDefinition field, ModuleDefinition module)
    {
        // Import the custom attributes references into the module
        foreach (var attribute in field.CustomAttributes) attribute.ImportReferences(module);

        // Import the field type reference into the module
        field.FieldType = module.ImportReference(field.FieldType);

        // Import the declaring type definition into the module
        field.DeclaringType = module.ImportReference(field.DeclaringType).Resolve();
    }

    /// <summary>
    /// Imports the property type and custom attributes references of a property into a module.
    /// </summary>
    /// <param name="property">The property whose references need to be imported.</param>
    /// <param name="module">The module type into whose module the references should be imported.</param>
    public static void ImportReferences(this PropertyDefinition property, ModuleDefinition module)
    {
        // Import the custom attributes references into the module
        foreach (var attribute in property.CustomAttributes) attribute.ImportReferences(module);

        // Import the property type reference into the module
        property.PropertyType = module.ImportReference(property.PropertyType);

        // Import the declaring type definition into the module
        property.DeclaringType = module.ImportReference(property.DeclaringType).Resolve();
    }

    /// <summary>
    /// Imports the parameter type and custom attributes references of a parameter into a module.
    /// </summary>
    /// <param name="parameter">The parameter whose references need to be imported.</param>
    /// <param name="module">The module type into whose module the references should be imported.</param>
    public static void ImportReferences(this ParameterDefinition parameter, ModuleDefinition module)
    {
        // Import the custom attributes references into the module
        foreach (var attribute in parameter.CustomAttributes) attribute.ImportReferences(module);

        // Import the parameter type reference into the module
        parameter.ParameterType = module.ImportReference(parameter.ParameterType);
    }

    /// <summary>
    /// Imports the variable type references of a variable into a module.
    /// </summary>
    /// <param name="variable">The variable whose type references need to be imported.</param>
    /// <param name="module">The module type into whose module the references should be imported.</param>
    public static void ImportReferences(this VariableDefinition variable, ModuleDefinition module)
    {
        // Import the variable type reference into the module
        variable.VariableType = module.ImportReference(variable.VariableType);
    }

    /// <summary>
    /// Imports the method type references and the custom attributes of a method into a module.
    /// </summary>
    /// <param name="method">The method whose references need to be imported.</param>
    /// <param name="module">The module type into whose module the references should be imported.</param>
    public static void ImportReferences(this MethodDefinition method, ModuleDefinition module)
    {
        // Import method overrides into the module
        for (int i = 0; i < method.Overrides.Count; ++i) method.Overrides[i] = module.ImportReference(method.Overrides[i]);

        // Import the custom attributes references into the module
        foreach (var attribute in method.CustomAttributes) attribute.ImportReferences(module);

        // Import the parameter type references into the module
        foreach (var parameter in method.Parameters) parameter.ImportReferences(module);

        // Import the return type reference into the module
        method.ReturnType = module.ImportReference(method.ReturnType);

        // Import the declaring type definition into the module
        method.DeclaringType = module.ImportReference(method.DeclaringType).Resolve();

        // If the method has a body, import references for each variable and instruction
        if (method.HasBody)
        {
            // Import the variable type references in the method body into the module
            foreach (var variable in method.Body.Variables) variable.ImportReferences(module);

            // Import the instruction type references in the method body into the module
            foreach (var instruction in method.Body.Instructions) instruction.ImportReferences(module);
        }
    }

    /// <summary>
    /// Imports the return type references of a CallSite into a module.
    /// </summary>
    /// <param name="callSite">The CallSite whose return type references need to be imported.</param>
    /// <param name="module">The module type into whose module the references should be imported.</param>
    public static void ImportReferences(this CallSite callSite, ModuleDefinition module)
    {
        // Import the return type reference of the callSite into the module
        callSite.ReturnType = module.ImportReference(callSite.ReturnType);
    }

    /// <summary>
    /// Imports the operand type references of an instruction into a module.
    /// </summary>
    /// <param name="instruction">The instruction whose operand references need to be imported.</param>
    /// <param name="module">The module type into whose module the references should be imported.</param>
    public static void ImportReferences(this Instruction instruction, ModuleDefinition module)
    {
        // Import the operand references of the instruction into the module
        if (instruction.Operand is ParameterDefinition parameter)
            parameter.ImportReferences(module);
        else if (instruction.Operand is VariableDefinition variable)
            variable.ImportReferences(module);
        else if (instruction.Operand is TypeReference type)
            instruction.Operand = module.ImportReference(type);
        else if (instruction.Operand is FieldReference field)
            instruction.Operand = module.ImportReference(field);
        else if (instruction.Operand is MethodReference method)
            instruction.Operand = module.ImportReference(method);
        else if (instruction.Operand is CallSite callSite)
            callSite.ImportReferences(module);
    }

    #endregion ImportReferences

    // Extension methods for swapping method implementations between different types.
    // This can be used when wanting to replace method functionality in the destination type with the corresponding functionality from the source type.
    #region SwapMethods

    /// <summary>
    /// Swaps the method references within the provided instruction between two given methods.
    /// </summary>
    /// <param name="instruction">The instruction to modify.</param>
    /// <param name="leftMethod">The first method to swap.</param>
    /// <param name="rightMethod">The second method to swap.</param>
    public static void SwapMethodReferences(this Instruction instruction, MethodDefinition leftMethod, MethodDefinition rightMethod)
    {
        // If the instruction's operand is a method reference
        if (instruction.Operand is MethodReference method)
        {
            // If the operand matches the left method, replace it with the right method
            if (method == leftMethod)
                instruction.Operand = rightMethod;
            // If the operand matches the right method, replace it with the left method
            else if (method == rightMethod)
                instruction.Operand = leftMethod;
        }
    }

    /// <summary>
    /// Swaps the method references within the provided collection of instructions between two given methods.
    /// </summary>
    /// <param name="instructions">The collection of instructions to modify.</param>
    /// <param name="leftMethod">The first method to swap.</param>
    /// <param name="rightMethod">The second method to swap.</param>
    public static void SwapMethodReferences(this Collection<Instruction> instructions, MethodDefinition leftMethod, MethodDefinition rightMethod)
    {
        // Swap method references for each instruction in the collection
        foreach (var instruction in instructions)
            instruction.SwapMethodReferences(leftMethod, rightMethod);
    }

    /// <summary>
    /// Swaps the method references within the body of the provided method between two given methods.
    /// </summary>
    /// <param name="method">The method to modify.</param>
    /// <param name="leftMethod">The first method to swap.</param>
    /// <param name="rightMethod">The second method to swap.</param>
    public static void SwapMethodReferences(this MethodDefinition method, MethodDefinition leftMethod, MethodDefinition rightMethod)
    {
        // Swap method references for each instruction in the method's body
        if (method.HasBody) method.Body.Instructions.SwapMethodReferences(leftMethod, rightMethod);
    }

    /// <summary>
    /// Swaps the attributes, parameters, custom attributes, and generic parameters between two given methods.
    /// </summary>
    /// <param name="leftMethod">The first method to swap.</param>
    /// <param name="rightMethod">The second method to swap.</param>
    public static void SwapMethods(this MethodDefinition leftMethod, MethodDefinition rightMethod)
    {
        // Save the left method's original details
        var leftBody = leftMethod.Body;
        var leftAttributes = leftMethod.Attributes;
        var leftImplAttributes = leftMethod.ImplAttributes;
        var leftSemanticsAttributes = leftMethod.SemanticsAttributes;
        var leftParameters = new Collection<ParameterDefinition>(leftMethod.Parameters);
        var leftCustomAttributes = new Collection<CustomAttribute>(leftMethod.CustomAttributes);
        var leftGenericParameters = new Collection<GenericParameter>(leftMethod.GenericParameters);

        // Swap the details from the right method to the left
        leftMethod.Body = rightMethod.Body;
        leftMethod.Body = rightMethod.Body;
        leftMethod.Attributes = rightMethod.Attributes;
        leftMethod.ImplAttributes = rightMethod.ImplAttributes;
        leftMethod.SemanticsAttributes = rightMethod.SemanticsAttributes;
        leftMethod.Parameters.Clear();
        leftMethod.CustomAttributes.Clear();
        leftMethod.GenericParameters.Clear();
        foreach (var parameter in rightMethod.Parameters) leftMethod.Parameters.Add(parameter);
        foreach (var attribute in rightMethod.CustomAttributes) leftMethod.CustomAttributes.Add(attribute);
        foreach (var parameter in rightMethod.GenericParameters) leftMethod.GenericParameters.Add(parameter);

        // Swap the details from the left method (which were saved) to the right
        rightMethod.Body = leftBody;
        rightMethod.Body = leftBody;
        rightMethod.Attributes = leftAttributes;
        rightMethod.ImplAttributes = leftImplAttributes;
        rightMethod.SemanticsAttributes = leftSemanticsAttributes;
        rightMethod.Parameters.Clear();
        rightMethod.CustomAttributes.Clear();
        rightMethod.GenericParameters.Clear();
        foreach (var parameter in leftParameters) rightMethod.Parameters.Add(parameter);
        foreach (var attribute in leftCustomAttributes) rightMethod.CustomAttributes.Add(attribute);
        foreach (var parameter in leftGenericParameters) rightMethod.GenericParameters.Add(parameter);

        // Swap method references within each method body
        leftMethod.SwapMethodReferences(leftMethod, rightMethod);
        rightMethod.SwapMethodReferences(rightMethod, leftMethod);
    }

    /// <summary>
    /// Finds and swaps methods with the same full name within the given type.
    /// </summary>
    /// <param name="type">The type to modify.</param>
    /// <param name="avoidSignatureConflicts">Avoid signature conflicts by changing original method parameters to be base object types</param>
    public static void SwapDuplicateMethods(this TypeDefinition type, bool avoidSignatureConflicts = false)
    {
        // This HashSet is used for tracking the methods that have already been swapped.
        var alreadySwapped = new HashSet<string>();

        // Convert the method collection to list for efficient index-based access.
        var methods = type.Methods.ToList();

        // Iterate over each pair of methods in the type
        for (int i = 0; i < methods.Count; i++)
        {
            for (int j = i + 1; j < methods.Count; j++)
            {
                var methodLeft = methods[i];
                var methodRight = methods[j];

                // If two methods have the same full name and haven't been swapped yet
                if (methodLeft.FullName == methodRight.FullName && !alreadySwapped.Contains(methodLeft.FullName))
                {
                    // Add the method full name to the set of already swapped methods
                    _ = alreadySwapped.Add(methodLeft.FullName);
                    // Swap the two methods
                    methodLeft.SwapMethods(methodRight);

                    // Change the original method types to be generic to avoid signature conflicts
                    if (avoidSignatureConflicts)
                    {
                        foreach (var parameter in methodRight.Parameters)
                            if (!parameter.ParameterType.IsValueType) parameter.ParameterType = type.Module.ImportReference(typeof(object));
                    }
                }
            }
        }
    }

    #endregion SwapMethods

    // Methods to do with instruction optimizations
    #region InstructionOptimizations

#pragma warning disable RCS1003
    /// <summary>
    /// Determines if a given instruction within a method can be optimized out.
    /// Specifically, this method looks for type conversion instructions (Isinst or Castclass)
    /// that are unnecessary because the type of the value at the top of the stack is
    /// already the target conversion type.
    /// </summary>
    /// <param name="instruction">The instruction to be checked for optimization.</param>
    /// <param name="method">The method definition that contains the instruction.</param>
    /// <returns>Returns true if the instruction can be optimized out. Otherwise, returns false.</returns>
    /// <remarks>
    /// This method works by examining the instructions before the given instruction in the method,
    /// maintaining a conceptual "stack balance" and tracking the type of the value at the top of the stack.
    /// The stack balance is a measure of the net effect of the instructions on the stack,
    /// with a positive balance indicating more values have been pushed than popped,
    /// and a negative balance indicating more values have been popped than pushed.
    /// If the stack balance is zero and the type of the value at the top of the stack
    /// matches the type conversion, the conversion is unnecessary and the method returns true.
    /// </remarks>
    public static bool CanBeOptimizedOut(this Instruction instruction, MethodDefinition method)
    {
        // Check if the instruction is a type conversion instruction (instance cast or class cast)
        if (instruction.OpCode == OpCodes.Isinst || instruction.OpCode == OpCodes.Castclass)
        {
            // Get the type to which the conversion is being made
            var typeConversionType = instruction.Operand as TypeReference;
            // Initialize stack balance. This will help to determine the net stack effect of the instructions
            int stackBalance = 0;
            // Move to the previous instruction
            instruction = instruction.Previous;

            // Process previous instructions
            while (instruction != null)
            {
                // Determine how the current instruction modifies the stack
                var pushBehaviour = instruction.OpCode.StackBehaviourPush;
                var popBehaviour = instruction.OpCode.StackBehaviourPop;

                // Fullname of any type extracted from the instruction
                string extractedFullName = null;

                // This is an exhaustive check for control flow change instructions. These instructions will cause a jump
                // in execution or a termination of the function, thus ending our analysis.
                if (instruction.OpCode == OpCodes.Ret || // Return from the current method.
                    instruction.OpCode == OpCodes.Throw || // Throw an exception.
                    instruction.OpCode == OpCodes.Rethrow || // Rethrow the current exception.
                    instruction.OpCode == OpCodes.Endfilter || // End the filter clause of an exception block and branch to the exception handler.
                    instruction.OpCode == OpCodes.Endfinally || // Transfer control from the exception block of a try or catch block.
                    instruction.OpCode == OpCodes.Leave || instruction.OpCode == OpCodes.Leave_S || // Exit a protected region of code.
                    instruction.OpCode == OpCodes.Jmp || // Jump to the method pointed to by the method pointer loaded on the stack.
                    instruction.OpCode == OpCodes.Switch || // Switch control to one of several locations.
                    instruction.OpCode == OpCodes.Br || instruction.OpCode == OpCodes.Br_S || // Unconditional branch to target.
                    instruction.OpCode == OpCodes.Brfalse || instruction.OpCode == OpCodes.Brfalse_S || // Branch to target if value is zero (false).
                    instruction.OpCode == OpCodes.Brtrue || instruction.OpCode == OpCodes.Brtrue_S || // Branch to target if value is non-zero (true).
                    instruction.OpCode == OpCodes.Beq || instruction.OpCode == OpCodes.Beq_S ||  // Branch to target if two values are equal.
                    instruction.OpCode == OpCodes.Bne_Un || instruction.OpCode == OpCodes.Bne_Un_S || // Branch to target if two values are not equal.
                    instruction.OpCode == OpCodes.Bge || instruction.OpCode == OpCodes.Bge_S || instruction.OpCode == OpCodes.Bge_Un || instruction.OpCode == OpCodes.Bge_Un_S || // Branch to target if value1 >= value2 (unsigned or unordered).
                    instruction.OpCode == OpCodes.Bgt || instruction.OpCode == OpCodes.Bgt_S || instruction.OpCode == OpCodes.Bgt_Un || instruction.OpCode == OpCodes.Bgt_Un_S || // Branch to target if value1 > value2 (unsigned or unordered).
                    instruction.OpCode == OpCodes.Ble || instruction.OpCode == OpCodes.Ble_S || instruction.OpCode == OpCodes.Ble_Un || instruction.OpCode == OpCodes.Ble_Un_S || // Branch to target if value1 <= value2 (unsigned or unordered).
                    instruction.OpCode == OpCodes.Blt || instruction.OpCode == OpCodes.Blt_S || instruction.OpCode == OpCodes.Blt_Un || instruction.OpCode == OpCodes.Blt_Un_S) // Branch to target if value1 < value2 (unsigned or unordered).
                    return false; // Return from method

                // Check if instruction is for loading a field onto the stack
                // In this case, the type of the value is the type of the field.
                else if (instruction.OpCode == OpCodes.Ldfld || // load field value onto stack
                         instruction.OpCode == OpCodes.Ldflda || // load field address onto stack
                         instruction.OpCode == OpCodes.Ldsfld || // load static field value onto stack
                         instruction.OpCode == OpCodes.Ldsflda) // load static field address onto stack
                    extractedFullName = ((FieldReference)instruction.Operand).FieldType.FullName;

                // Check if instruction is for loading an argument onto the stack
                // In this case, the type of the value is the type of the argument.
                else if (instruction.OpCode == OpCodes.Ldarg || // load argument onto stack
                         instruction.OpCode == OpCodes.Ldarg_S) // short form for loading argument onto stack
                    extractedFullName = ((ParameterReference)instruction.Operand).ParameterType.FullName;

                // Check for loading argument at index 0 onto stack
                else if (instruction.OpCode == OpCodes.Ldarg_0) // load argument at index 0 onto stack
                    extractedFullName = (method.IsStatic ? method.Parameters[0].ParameterType : method.DeclaringType).FullName;
                // Check for loading argument at index 1 onto stack
                else if (instruction.OpCode == OpCodes.Ldarg_1) // load argument at index 1 onto stack
                    extractedFullName = (method.IsStatic ? method.Parameters[1].ParameterType : method.Parameters[0].ParameterType).FullName;
                // Check for loading argument at index 2 onto stack
                else if (instruction.OpCode == OpCodes.Ldarg_2) // load argument at index 2 onto stack
                    extractedFullName = (method.IsStatic ? method.Parameters[2].ParameterType : method.Parameters[1].ParameterType).FullName;
                // Check for loading argument at index 3 onto stack
                else if (instruction.OpCode == OpCodes.Ldarg_3) // load argument at index 3 onto stack
                    extractedFullName = (method.IsStatic ? method.Parameters[3].ParameterType : method.Parameters[2].ParameterType).FullName;

                // Check for loading local variable onto stack
                else if (instruction.OpCode == OpCodes.Ldloc || // load local variable onto stack
                         instruction.OpCode == OpCodes.Ldloc_S) // short form for loading local variable onto stack
                    extractedFullName = ((VariableReference)instruction.Operand).VariableType.FullName;
                // Check for loading local variable at index 0 onto stack
                else if (instruction.OpCode == OpCodes.Ldloc_0) // load local variable at index 0 onto stack
                    extractedFullName = method.Body.Variables[0].VariableType.FullName;
                // Check for loading local variable at index 1 onto stack
                else if (instruction.OpCode == OpCodes.Ldloc_1)// load local variable at index 1 onto stack
                    extractedFullName = method.Body.Variables[1].VariableType.FullName;
                // Check for loading local variable at index 2 onto stack
                else if (instruction.OpCode == OpCodes.Ldloc_2)// load local variable at index 2 onto stack
                    extractedFullName = method.Body.Variables[2].VariableType.FullName;
                // Check for loading local variable at index 3 onto stack
                else if (instruction.OpCode == OpCodes.Ldloc_3)// load local variable at index 3 onto stack
                    extractedFullName = method.Body.Variables[3].VariableType.FullName;

                // Check for calling a method and pushing return value onto the stack, loading function pointer onto the stack
                else if (instruction.OpCode == OpCodes.Callvirt || // call method virtually and push return value onto stack
                         instruction.OpCode == OpCodes.Call || // call method and push return value onto stack
                         instruction.OpCode == OpCodes.Ldftn || // load method pointer onto stack
                         instruction.OpCode == OpCodes.Ldvirtftn) // load virtual method pointer onto stack
                    extractedFullName = ((MethodReference)instruction.Operand).ReturnType.FullName;

                // Check for calling a method indicated on the stack with arguments, pushing return value onto stack
                else if (instruction.OpCode == OpCodes.Calli) // call method indicated on the stack with arguments, pushing return value onto stack
                    extractedFullName = ((CallSite)instruction.Operand).ReturnType.FullName;

                // Check for creating a new object and pushing object reference onto stack
                else if (instruction.OpCode == OpCodes.Newobj) // create a new object and push object reference onto stack
                    extractedFullName = ((MethodReference)instruction.Operand).DeclaringType.FullName;

                // Check for loading an object, array element, or pointer onto stack, creating a new array, or creating a typed reference
                else if (instruction.OpCode == OpCodes.Ldobj || // load object onto stack
                         instruction.OpCode == OpCodes.Ldelem_Any || // load element of an object array onto stack
                         instruction.OpCode == OpCodes.Newarr || // create a new array and push reference onto stack
                         instruction.OpCode == OpCodes.Mkrefany) // push a typed reference onto stack
                    extractedFullName = ((TypeReference)instruction.Operand).FullName;

                // Check for loading a string onto stack
                else if (instruction.OpCode == OpCodes.Ldstr) // load a string onto stack
                    extractedFullName = typeof(string).FullName;

                // If the type of the value currently at the top of the stack matches the type conversion
                // and the stack is balanced, the conversion is unnecessary
                if (stackBalance == 0 && extractedFullName == typeConversionType.FullName)
                    return true;

                // Dup doesn't change the type of the top of the stack, so adjust stack balance to ignore it
                if (instruction.OpCode == OpCodes.Dup)
                    stackBalance--;

                // Adjust stack balance according to the current instruction's push behavior
                //if (pushBehaviour == StackBehaviour.Push0)
                if (pushBehaviour == StackBehaviour.Push1 || pushBehaviour == StackBehaviour.Pushi || pushBehaviour == StackBehaviour.Pushref ||
                    pushBehaviour == StackBehaviour.Pushi8 || pushBehaviour == StackBehaviour.Pushr4 || pushBehaviour == StackBehaviour.Pushr8 ||
                    pushBehaviour == StackBehaviour.Varpush)
                    stackBalance++;
                else if (pushBehaviour == StackBehaviour.Push1_push1)
                    stackBalance += 2;

                // Adjust stack balance according to the current instruction's pop behavior
                //if (popBehaviour == StackBehaviour.Pop0)
                if (popBehaviour == StackBehaviour.Pop1 || popBehaviour == StackBehaviour.Popi || popBehaviour == StackBehaviour.Popref ||
                         popBehaviour == StackBehaviour.Varpop)
                    stackBalance--;
                else if (popBehaviour == StackBehaviour.Pop1_pop1 || popBehaviour == StackBehaviour.Popi_popi || popBehaviour == StackBehaviour.Popi_pop1 ||
                         popBehaviour == StackBehaviour.Popi_popi8 || popBehaviour == StackBehaviour.Popi_popr4 || popBehaviour == StackBehaviour.Popi_popr8 ||
                         popBehaviour == StackBehaviour.Popref_pop1 || popBehaviour == StackBehaviour.Popref_popi)
                    stackBalance -= 2;
                else if (popBehaviour == StackBehaviour.Popi_popi_popi || popBehaviour == StackBehaviour.Popref_popi_popi || popBehaviour == StackBehaviour.Popref_popi_popi8 ||
                         popBehaviour == StackBehaviour.Popref_popi_popr4 || popBehaviour == StackBehaviour.Popref_popi_popr8 || popBehaviour == StackBehaviour.Popref_popi_popref)
                    stackBalance -= 3;

                // Move to previous instruction
                instruction = instruction.Previous;
            }
        }

        // Return that the instruction cannot be optimized
        return false;
    }
#pragma warning restore RCS1003

    /// <summary>
    /// Optimizes a given method by removing any instructions
    /// that can be optimized out.
    /// </summary>
    /// <param name="method">
    /// The MethodDefinition object to be optimized. This method contains a list
    /// of instructions that are to be checked and potentially removed if they can be optimized out.
    /// </param>
    public static void OptimizeInstructions(this MethodDefinition method)
    {
        // If the method doesn't have a body (i.e., it's an abstract or external method), then exit
        if (!method.HasBody) return;

        // Iterate over each instruction in the method body
        for (int i = 0; i < method.Body.Instructions.Count - 1; ++i)
        {
            // If the current instruction can be optimized out according to the CanBeOptimizedOut method, remove it
            if (method.Body.Instructions[i].CanBeOptimizedOut(method))
            {
                // Remove the current instruction from the method body
                method.Body.Instructions.RemoveAt(i);

                // Decrement the loop index to account for the removal. This ensures that the next iteration doesn't skip any instructions.
                i--;
            }
        }
    }

    #endregion InstructionOptimizations
}
