/Leo

A library based on NCC Natasha to quickly and easily read/write instance's fields or properties.

Primary LanguageC#MIT LicenseMIT

中文 | English

Leo

A high-performance type dynamic operation library.

Member project of Night Moon Studio NuGet Badge GitHub repo size Badge GitHub license

This project is based on NCC Natasha and .NET.

Automatically build high-performance operation agent classes through runtime. Provides complete high-performance operations for ordinary classes, static classes, dynamic classes, nested dynamic classes, and dynamically generated static classes.

Member index and type cache are rebuilt using high-performance algorithms. If reflection, Dynamic and other methods cannot meet your special needs, you can choose to use this solution.

CI Build Status

CI Platform Build Server Master Test
Github os Build status

Getting Started

Install

Leo can be installed in your project with the following command.

PM> Install-Package NMS.Leo
PM> Install-Package NMS.Leo.Typed

Natasha initialization

// Only register components
NatashaInitializer.Initialize();

// or
// Register the component and warm up the component, 
// the runtime compilation speed will be faster.
await NatashaInitializer.InitializeAndPreheating();

Core Usage

Leo uses NCC BTFindTree Algorithm as the method search algorithm, and uses Precision by default to build the index of properties and fields.

Create Dictionary Operator

Use precision minimum weight to build properties and fields index:

var handler = PrecisionDictOperator.CreateFromType(typeof(A));
// or
var handler = PrecisionDictOperator<A>.Create();

Use hash binary search to build properties and fields index:

var handler = HashDictOperator.CreateFromType(typeof(A));
// or
var handler = HashDictOperator<A>.Create();

Use fuzzy pointer search to build properties and fields index:

var handler = FuzzyDictOperator.CreateFromType(typeof(A));
// or
var handler = FuzzyDictOperator<A>.Create();

How to use the dictionary operator

Suppose there are two types A and B:

public class A
{
   public int Age;
   public DateTime Time;
   public B Outter = new B();
}

public class B
{
   public string Name;
   public B()
   {
      Name = "小明"
   }
}

Then we can call the dictionary operator like this:

var handler = PrecisionDictOperator.CreateFromType(typeof(A));
handler.New();

handler["Age"]= 100;                                          // Set operation
handler.Set("Age", 100);                                      // Set operation

Console.WriteLine(handler["Time"]);                           // Get operation
Console.WriteLine(handler.Get<DateTime>("Time"));             // Get operation

((B)handler["Outter"]).Name = "NewName";                      // Link operation

Build Dynamic Type

We first prepare a piece of text:

string text = @"
using System;
using System.Collections;
using System.Linq;
using System.Text;
 
namespace HelloWorld
{
    public class Test
    {
        public Test(){
            Name=""111"";
            Pp = 10;
            Rp=""aa"";
        }
        private long Pp;
        private readonly string Rp;
        public string Name;
        public int Age{get;set;}
    }
}";

Then build the runtime type through Natasha:

var oop = new AssemblyCSharpBuilder();
oop.Add(text);
Type type = oop.GetTypeFromShortName("Test");

Finally, use Leo to manipulate instances of this runtime type:

var instance = PrecisionDictOperator.CreateFromType(type);

// If you use LeoVisitor in NMS.Leo.Typed, these two parts are done automatically
var obj = Activator.CreateInstance(type);
instance.SetObjInstance(obj);

instance["Pp"] = 30L;
instance["Rp"] = "ab";
instance.Set("Name", "222");

Leo Visitor

An easier-to-use package is provided in the NMS.Leo.Typed package.

Reference namespace:

using NMS.Leo.Typed;

Create Visitor Instance

Leo Visitor supports creation through Type and Generic parameter:

var type = typeof(YourType);
var visitor = LeoVisitorFactory.Create(type); // returns ILeoVisitor instance

// or
var visitor = LeoVisitorFactory.Create<YourType>(); // returns ILeoVisitor<YourType> instance

Initialize Leo Visitor with an Object Instance

You can specify an existing object for Leo Visitor:

var type = typeof(YourType);
var instance = new YourType();

// Give the instance object directly in the factory method.
var visitor = LeoVisitorFactory.Create(type, instance); // returns ILeoVisitor instance

// or
var visitor = LeoVisitorFactory.Create<YourType>(instance); // returns ILeoVisitor<YourType> instance

Then get the instance object from Leo Visitor:

object instance = visitor.Instance; // Obtain the object object from ILeoVisitor.

// or
T instance = visitor.Instance; // Obtain an instance of type T from ILeoVisitor<T>.

Initialize Leo Visitor with a Dictionary

When creating Leo Visitor, you can also use a dictionary to directly initialize the instance:

var d = new Dictionary<string, object>();
d["Name"] = "YourMidName";
d["Age"] = 25;

var type = typeof(YourType);
var visitor = LeoVisitorFactory.Create(type, d); // returns ILeoVisitor

// or
var visitor = LeoVisitorFactory.Create<YourType>(d); // returns ILeoVisitor<YourType>

Then get the instance object from Leo Visitor:

object instance = visitor.Instance; // Obtain the object object from ILeoVisitor.

// or
T instance = visitor.Instance; // Obtain an instance of type T from ILeoVisitor<T>.

Set or Get Value

The value in Leo Visitor can be read and written through the GetValue or SetValue method.

GetValue

Read the value of the field or property named Name from Leo Visitor:

var visitor = LeoVisitorFactory.Create(typeof(YourType)); // ILeoVisitor

object name = visitor.GetValue("Name");

// or
string name = visitor.GetValue<string>("Name");

// or
object name = visitor["Name"];

// or
object name = visitor.GetValue<YourType>(t => t.Name);

// or
string name = visitor.GetValue<YourType, string>(t => t.Name);

// or
string name = visitor.GetValue(t => t.Name); // only for ILeoVisitor<YourType>

Or get the values of all fields or properties at once through a dictionary:

var d = visitor.ToDictionary(); // Dictionary<string, object>

SetValue

Set the value YourName to the field or property named Name.

var visitor = LeoVisitorFactory.Create(typeof(YourType)); // ILeoVisitor

visitor.SetValue("Name", "YourName");

// or
visitor["Name"] = "YourName";

// or
visitor.SetValue<YourType>(t => t.Name, "YourName");

// or
visitor.SetValue<YourType, string>(t => t.Name, "YourName");

// or
visitor.SetValue<string>(t => t.Name, "YourName"); // only for ILeoVisitor<YourType>

You can even batch operations directly through the dictionary:

var d = new Dictionary<string, object>();
d["Name"] = "YourMidName";
d["Age"] = 25;

visitor.SetValue(d);

Select the members to be returned

We can use Select to select and return the members we need:

var visitor = LeoVisitorFactory.Create(typeof(YourType)); // ILeoVisitor

var z0 = v.Select((name, val) => name);                         // returns ILeoSelector<YourType, string>
var z1 = v.Select((name, val, metadata) => name);               // returns ILeoSelector<YourType, string>
var z2 = v.Select(ctx => ctx.Name);                             // returns ILeoSelector<YourType, string>
var z3 = v.Select(ctx => (ctx.Name, ctx.Index));                // returns ILeoSelector<YourType, (Name, Index)>
var z4 = v.Select(ctx => new {ctx.Name, ctx.Value, ctx.Index}); // returns ILeoSelector<YourType, {Name, Value, Index}>

At this point we will get an implementation of the ILeoSelector interface, we only need to execute the FireAndReturn() method to get the result we need:

var l0 = z0.FireAndReturn(); // returns IEnumerable<string>
var l1 = z1.FireAndReturn(); // returns IEnumerable<string>
var l2 = z2.FireAndReturn(); // returns IEnumerable<string>
var l3 = z3.FireAndReturn(); // returns IEnumerable<(Name, Index)>
var l4 = z4.FireAndReturn(); // returns IEnumerable<{Name, Value, Index}>

Leo Getter and Setter

We can use the built-in LeoGetter and LeoSetter to fluently create a Getter or a Setter for an instance or value.

Instance Reader: ILeoGetter

We can get the value of its member (properties or fields) from the instance through the instance reader (exposed as ILeoGetter).

var type = typeof(YourType);
var act = new YourType()
{
    Name = "YourName",
    Age = 22,
    Country = Country.China,
    Birthday = DateTime.Today
};

var getter = LeoGetter.Type(type).Instance(act); // returns ILeoGetter

// or
var getter = LeoGetter.Type<YourType>().Instance(act); // return ILeoGetter<YourType>

When creating an instance reader, we can initialize it through a dictionary. At this time, the instance reader will construct an object by itself.

var d = new Dictionary<string, object>();
d["Name"] = "YourName";

var getter = LeoGetter.Type(type).InitialValues(d); // returns ILeoGetter

Then, we can read the value within the instance:

var val = getter.GetValue<string>("Name");

Essentially, the instance reader is a read-only ILeoVisitor.

Instance Setter: ILeoSetter

We can set values to the members (properties or fields) of the instance through the built-in instance setter (exposed as ILeoSetter).

var type = typeof(YourType);

You can pass the instance directly into the instance setter:

var act = new YourType()
{
    Name = "YourName",
    Age = 22,
    Country = Country.China,
    Birthday = DateTime.Today
};

var setter = LeoSetter.Type(type).Instance(act); // ILeoSetter

// or
var setter = LeoSetter.Type<YourType>().Instance(act); // ILeoSetter<YourType>

Or use a dictionary:

var d = new Dictionary<string, object>();
d["Name"] = "YourName";

var setter = LeoSetter.Type(type).InitialValues(d); // ILeoSetter

Or automatically create a new object:

var setter = LeoSetter.Type(type).NewInstance(); // ILeoSetter

// or
var setter = LeoSetter.Type<NiceAct>().NewInstance(); // ILeoSetter<YourType>

Then, we can set the value in the instance:

setter.SetValue("Name", "YourMidName");

Essentially, the instance setter is a write-only ILeoVisitor.

Value Reader: ILeoValueGetter

Through the value reader, we can read a member (property or field) in the instance at a finer granularity, and the value reader can prevent users from reading the value of an unrelated member (properties or fields) .

var type = typeof(YourType);

var fluentGetter = LeoGetter.Type(type).Value("Name"); // returns IFluentValueGetter

// or
var fluentGetter = LeoGetter.Type<YourType>().Value("Name"); // returns IFluentValueGetter<YourType>

// or
var fluentGetter = LeoGetter.Type<YourType>().Value(t => t.Name); // returns IFluentValueGetter<YourType>

// or
var fluentGetter = LeoGetter.Type<YourType>().Value<string>(t => t.Name); // returns IFluentValueGetter<YourType>

Then, we specify a specific instance for fluentGetter:

var act = new YourType()
{
    Name = "YourName",
    Age = 22,
    Country = Country.China,
    Birthday = DateTime.Today
};

var getter = fluentGetter.Instance(act); // ILeoValueGetter

Finally, we can read the specified member (property or field) from the instance:

var val = getter.Value;

Value Setter: ILeoValueSetter

Through the value setter, we can write to a member (property or field) in the instance with finer granularity, and the value setter can prevent users from setting irrelevant members (properties or fields).

var type = typeof(YourType);

var fluentSetter = LeoSetter.Type(type).Value("Name"); // return IFluentValueSetter

// or
var fluentSetter = LeoSetter.Type<NiceAct>().Value("Name"); // return IFluentValueSetter<YourType>

// or
var fluentSetter = LeoSetter.Type<NiceAct>().Value(t => t.Name); // return IFluentValueSetter<YourType>

// or
var fluentSetter = LeoSetter.Type<NiceAct>().Value<string>(t => t.Name); // return IFluentValueSetter<YourType>

Then, we specify a specific instance for fluentSetter:

var act = new YourType()
{
    Name = "YourName",
    Age = 22,
    Country = Country.China,
    Birthday = DateTime.Today
};

var setter = fluentSetter.Instance(act); // ILeoValueSetter

Finally, we can set values to the specified member (property or field) of the instance:

setter.Value("YourLastName");

Leo Metadata

You can get the metadata of the field or property from the dictionary operator:

var instance = PrecisionDictOperator<YourType>.Create(); // DictBase<YourType>

var members = instance.GetMembers(); // IEnumerable<LeoMember>

// Or get only readable/writable members
var members = instance.GetCanReadMembers();
var members = instance.GetCanWriteMembers();

// Or get the metadata of the property or field of the specified name
var member = instance.GetMember("Name"); // LeoMember

You can get the metadata of fields or properties from Leo Visitor:

var visitor = LeoVisitorFactory.Create(typeof(YourType)); // ILeoVisitor

// Use the specified name to get the metadata of the corresponding property or field
var member = visitor.GetMember("Name"); // LeoMember

// Or directly specify the member of this type to get the metadata of the property or field
var member = visitor.GetMember( t => t.Name ); // Only for ILeoVisitor<YourType>

Release Notes

  • 2019-08-01: Release v1.0.0.0, a high-performance dynamic calling library.
  • 2020-10-12: Release v1.2.0.0, use the latest version of Natasha and DynamicCache, and use function pointers instead of system delegates.

Algorithm

Performance

Performance

License

FOSSA Status

MIT