Table of Contents
AbysmalCore.Extensibility

Getting Started with AbysmalCore Extensibility

The AbysmalCore Extensibility Framework provides a simple, uniform interface for dynamically loading, compiling, and interacting with C# code using reflection. This guide will walk you through the core concepts and steps for using the ExtensibilityHelper, UniformAssembly, UniformClass, UniformMethod, and UniformProperty classes.

1. Core Concepts

The framework revolves around wrapping the standard .NET Reflection types (Assembly, Type, MethodInfo, PropertyInfo, FieldInfo) into Uniform classes. This abstraction is designed to make common reflection tasks easier and more consistent.

Uniform Class Wraps Description
UniformAssembly Assembly Represents a compiled assembly, used to access its classes and invoke its entry point.
UniformClass Type & object Represents an instantiated class from an assembly, providing access to its methods and properties.
UniformMethod MethodInfo Represents a method, used for invoking it with arguments.
UniformProperty PropertyInfo or FieldInfo Represents a property or field, used for getting and setting its value.

2. Dynamic Compilation and Loading

The ExtensibilityHelper class is your starting point for turning raw C# source code into an executable assembly.

Step 1: Compiling Source Code

Use ExtensibilityHelper.CompileAssembly to compile a string of C# code into a standard System.Reflection.Assembly.

using System.Reflection;
using AbysmalCore.Extensibility;

string sourceCode = """
namespace MyExtension
{
    public class MyClass
    {
        public string GetMessage() => "Hello from the dynamically loaded assembly!";
        public int Count { get; set; } = 0;
    }
}
""";

// internally we use CSharp.CodeAnalysis to compile
Assembly compiledAssembly = ExtensibilityHelper.CompileAssembly(sourceCode);

Step 2: Creating a Uniform Assembly

Once you have the Assembly, wrap it in a UniformAssembly to begin interacting with its contents.

// wrap the compiled assembly
// 'getPrivate = false' means only public members are exposed (default)
UniformAssembly uniformAssembly = ExtensibilityHelper.LoadAssembly(compiledAssembly);

bool hasClass = uniformAssembly.HasClass("MyExtension.MyClass"); // true
Important

Classes are found and retrieved using their full name. If your class is in a namespace, make sure to include its full path:

MyClass

MyExtension.MyClass

3. Interacting with Classes and Members

With the UniformAssembly, you can now instantiate classes and manipulate their methods and properties using the rest of the Uniform classes.

Step 3: Getting and Instantiating a Uniform Class

Retrieve the UniformClass by its name and get the instantiated object through the Instance property.

// get the wrapper for the class
UniformClass? myUniformClass = uniformAssembly.GetClass("MyExtension.MyClass");

if (myUniformClass != null)
{
    // the Instance property holds an object of the underlying class (MyExtension.MyClass)
    object myInstance = myUniformClass.Instance;
    
    // you can create a new instance as well
    object newInstance = myUniformClass.New(); 
}
Tip

If your wrapped class implements a common interface or abstraction, use UniformClass.DeriveFrom<T> to create an instance of that interface or abstraction with the class's overrides and implementations


Step 4: Invoking Methods

Use UniformClass.GetMethod to retrieve a method wrapper, then use its Invoke methods.

if (myUniformClass != null)
{
    UniformMethod? getMessageMethod = myUniformClass.GetMethod("GetMessage");

    if (getMessageMethod != null)
    {
        // invoke the method with no arguments
        object? result = getMessageMethod.Invoke(); 

        // invoke and cast the result
        string message = getMessageMethod.Invoke<string>(); // "Hello from the dynamically loaded assembly!"
    }
}

Step 5: Accessing Properties and Fields

Use UniformClass.GetProperty to retrieve a property/field wrapper, then use its Value property to get or set.

if (myUniformClass != null)
{
    UniformProperty? countProperty = myUniformClass.GetProperty("Count");

    if (countProperty != null)
    {
        // get the value (returns an object, so casting is often necessary)
        int currentValue = (int)countProperty.Value!; // 0

        // set the value
        countProperty.Value = 42;

        // verify new value
        int newValue = (int)countProperty.Value!; // 42
    }
}

4. Special Scenarios

Invoking an Assembly Entry Point

If your dynamically compiled assembly has a Main method (an entry point), you can invoke it directly.

// lets assume your source code has a public static void Main(string[] args)
object? output;
string[] cliArgs = { "arg1", "arg2" };

bool invoked = uniformAssembly.InvokeEntrypoint(cliArgs, out output);

if (invoked)
{
    // 'output' will contain the result of the entry point method (e.g., an integer if it returns int)
}

Exposing Private Members

When creating a UniformAssembly or UniformClass, passing true for getPrivate will include non-public instance properties and fields in the resulting collections.

// this UniformClass instance will include private members
UniformClass privateClass = new UniformClass(myInstance, getPrivate: true); 

// note: you can check if a member is private via the IsPrivate property
author author