[AI Readability Summary]
AScript is a .NET-focused dynamic C# scripting library that turns functions into a core engine abstraction. It supports script-defined functions, host method injection, function retrieval, Lambda expression generation, and delegate compilation. This design helps teams avoid hardcoded business rules, improve extensibility, and integrate scripts with host applications more naturally.
Technical Specification Snapshot
| Parameter | Description |
|---|---|
| Project Name | AScript |
| Core Language | C# / .NET |
| Key Capabilities | Dynamic script parsing, function execution, Expression/Lambda generation |
| Function Sources | Defined inside scripts, injected through ScriptContext, injected from Root, injected at the scripting-language level |
| Runtime Model | Interpreted execution + compiled delegate generation |
| License | Open-source project; the original source does not explicitly specify the license |
| Star Count | Not provided in the original data |
| Core Dependencies | .NET Expression, ScriptContext, CustomFunction, IFunctionEvaluator |
| Repository | https://gitee.com/rockey627/AScript |
AScript Makes the Function System a Core Abstraction of the Script Engine
AScript does more than execute a string of code. It turns the function system into a unified integration point. Developers can declare functions inside scripts, or inject delegates, static methods, instance methods, and even custom evaluators from C#.
This design directly addresses common rule-engine pain points. When business logic changes frequently, you do not need to keep modifying host code. When a script needs to reuse application capabilities, you also avoid writing large amounts of glue code.
Script-Defined Functions Are Well Suited for Configurable Business Rules
Script function definitions are close to C# syntax, which keeps migration costs low. AScript supports standard functions, expression-bodied functions, anonymous functions, recursive functions, and overloads. As a result, even complex rules can remain structured and readable.
var script = new Script();
string code = @"
int sum(int a, int b) => a + b; // Expression-bodied function
int fib(int n) // Recursive function
{
if (n <= 1) return n; // Recursion base case
return fib(n - 1) + fib(n - 2); // Recursive calculation
}
sum(3, 5) + fib(6); // 8 + 8 = 16
";
var result = script.Eval(code);
This example shows how AScript can combine expression-bodied functions and recursive functions within the same script.
Anonymous Functions and Overloads Make Scripts Feel Like a Real Programming Language
Anonymous functions use an underscore _ as the function name. In practice, this allows you to assign a function object to a variable and invoke it through Invoke. Function overloading lets multiple implementations share the same name while selecting behavior based on the parameter signature, which is especially useful in general-purpose computation scenarios.
var script = new Script();
string code = @"
var sum = int _(int a, int b) => a + b; // Define an anonymous function and assign it
int calc(int a, int b) => a + b; // Two-parameter overload
int calc(int a, int b, int c) => a + b + c; // Three-parameter overload
sum.Invoke(3, 5) + calc(1, 2, 3); // 8 + 6 = 14
";
var result = script.Eval(code);
This demonstrates that AScript does not merely run scripts. It also provides function organization capabilities that are much closer to a full programming language.
External Function Injection Is the Key Mechanism for Integrating AScript with Host Assemblies
In real projects, scripts almost always need to call capabilities that already exist in the host system, such as math utilities, random number generation, helper methods, or domain services. AScript exposes a unified injection entry point through ScriptContext.
AI Visual Insight: This image shows the overload list for function injection APIs on ScriptContext. It highlights that AddFunc and AddAction support generic signatures with multiple parameters, indicating that the framework separates functions with return values from procedures without return values. This makes it well suited for safely mapping C# methods into the script runtime.
AI Visual Insight: This image further shows the signature details of the function registration interfaces. It reflects that AScript distinguishes parameter count, delegate type, and name binding strategy during registration. That means call dispatch is not based on simple string matching, but on a registration model with type constraints.
AI Visual Insight: This image presents additional overloads for extensible injection, showing that AScript supports not only direct delegate injection but also scanning methods from types or object instances. This significantly reduces host integration effort.
Direct Delegate Injection Is the Lightest-Weight Integration Option
var script = new Script();
script.Context.AddFunc<int, int, int>("sum", (a, b) => a + b); // Inject a C# delegate
var result = script.Eval("sum(3,5)"); // Call it directly inside the script
This approach is ideal for injecting a small set of stable utility functions.
Injecting Static and Instance Methods Is Better for Reusing Existing Libraries
Static method injection works well for utility classes such as Math. Instance method injection is better for stateful objects such as Random. This lets scripts call host methods in an almost native way.
var random = new Random();
var script = new Script();
script.Context.AddFunc
<Math>(); // Inject public static methods
script.Context.AddFunc(random); // Inject public instance methods
var value = script.Eval("Abs(-8)"); // Actually calls Math.Abs
This example reflects a common architecture: the script layer issues the call, and the host layer provides the implementation.
Custom Function Evaluators Let AScript Extend Language Semantics
When ordinary delegates cannot express dynamic parameters, dynamic return types, or custom compilation behavior, you can implement IFunctionEvaluator. If needed, you can also add IFunctionBuilder to control expression tree generation. In the original example, this mechanism is used to implement the power operator **.
public class PowerOperator : IFunctionEvaluator, IFunctionBuilder
{
public void Build(FunctionBuildArgs e)
{
var left = e.Args[0].Build(e.BuildContext, e.ScriptContext, e.Options);
var right = e.Args[1].Build(e.BuildContext, e.ScriptContext, e.Options);
var result = Expression.Call(typeof(Math).GetMethod("Pow")!,
Expression.Convert(left, typeof(double)), // Convert the left operand to double
Expression.Convert(right, typeof(double))); // Convert the right operand to double
e.Result = result;
}
}
The significance is clear: AScript supports not only function injection, but also language-level behavior extension. That makes it closer to a customizable scripting language core.
CustomFunction Unifies the Internal Representation of Script Functions
In AScript, functions defined inside scripts are ultimately parsed into CustomFunction instances and added to the context. This abstraction stores the function name, parameter names, parameter types, return type, and syntax tree node. In other words, a script function is not temporary text. It becomes a reusable runtime object.
Function Retrieval, Lambda Generation, and Delegate Compilation Improve Reusability
After a function has been executed, developers can retrieve it from ScriptContext as a strongly typed delegate. This means rules defined in scripts can be reused in later processing stages instead of being consumed only once.
var script = new Script();
script.Eval(@"int sum(int a, int b) => a + b; sum(3,5);");
var sum = script.Context.GetFunc<int, int, int>("sum"); // Retrieve the script function
var value = sum(10, 20); // Reuse it later
This gives scripts an engineering-friendly workflow: define first, bind later, and reuse repeatedly.
Lambda Generation Can Connect Directly to LINQ Query Pipelines
script.Lambda<T, bool>() can convert a dynamic condition script into an expression tree for IQueryable scenarios. This allows filter expressions supplied by a frontend or configuration center to enter the database query pipeline directly.
var script = new Script();
var where = script.Lambda<Person, bool>("p.Name=='tom' || p.Name=='jim'", "p"); // Generate an expression tree
var list = query.Where(where).ToList(); // Let the LINQ provider continue translation
This capability marks an important step in AScript’s evolution from a script executor to a query expression builder.
Compiled Delegates Are Better for High-Frequency Invocation Scenarios
If a piece of logic will run repeatedly, compiling it into a delegate is more efficient than parsing script text every time.
var script = new Script();
var sum = script.Compile<int, int, int>("a+b", "a", "b"); // Compile into a reusable delegate
var result = sum(3, 5); // Better suited for frequent calls
This example shows that AScript covers both interpreted execution and precompiled execution models.
AScript’s Function System Fits Rule Engines and Dynamic Business Orchestration
Overall, AScript’s function design offers three major advantages. First, its script syntax is close to C#, which keeps the learning curve low. Second, its host injection mechanism is complete, making it easy to integrate existing .NET capabilities. Third, it supports expression tree generation and delegate compilation, which naturally connects to LINQ and high-performance execution scenarios.
For .NET projects that need configurable rules, a strongly typed host, and an extensible execution model, AScript’s function system provides strong practical engineering value.
FAQ
1. What is the biggest difference between AScript and a typical Eval library?
AScript does more than return a computation result. It provides a complete function system that includes script-defined functions, external function injection, Lambda generation, and delegate compilation, making it a better fit for production-grade dynamic rule scenarios.
2. Should I prefer script-defined functions or externally injected functions?
Use script-defined functions for logic that changes frequently and should remain configuration-driven. Use externally injected C# functions for stable, reusable behavior that depends on host objects or existing application services.
3. When should I use Lambda or Compile?
Use Lambda when the script needs to participate in IQueryable query translation. Use Compile when the same logic must run repeatedly at high frequency and you want to reduce runtime overhead by generating a delegate.
Core Summary: This article systematically explains AScript’s function capabilities, including script-defined functions, external function injection, function retrieval, Lambda generation, and delegate compilation, helping .NET developers quickly build a highly extensible dynamic script execution system.