Dynamic generation of entity classes (iii)

Source: Internet
Author: User
Tags creative commons attribution

Objective

There are two main ways of dynamically generating and compiling in. NET, one is through the System.Linq.Expressions method of the class in the namespace, LambdaExpression CompileToMethod(...) but this method only supports dynamic compilation to static method, because this restriction we can only abandon it and adopt emitting to generate the compiling scheme, although Emitt ING solution is powerful but it is cumbersome to implement, you must manually deal with the various details of the underlying IL, the implementation mechanism of some C # compiler, and also understand some basic il(Intermediate L Anguage) and the CLR(JVM) execution knowledge.

Basic knowledge

Because to adopt the emitting technology scheme, must understand IL, if you have not been how to contact before, do not lose heart, there is a lot of on-line about IL introductory article, "30 minutes to get started" or no problem ha, after all, IL relative to 8086/8088 assembly, really approachable too much.

First you need a tool like Ilspy (http://ilspy.net) to view the generated IL or the decompile assembly (the latest version ofIlspy also provides a comparison of IL with the corresponding C #, The user experience is really thoughtful.

One, unlike the 8086/8088 register-based instruction set, IL and Java bytecode are stack-based instruction sets, the most obvious difference is the way the parameters of the instruction are specified in different ways. As an example of an "int x = 100+200" operation, the instruction sequence for IL is roughly:

ldc.i4 100ldc.i4 200addstlocl.0
    • The first two lines load 100 and 200 of these 32-bit integers into the Operation Stack (Evaluation Stack);
    • The 3rd line of add is an addition operation instruction, which pops up (POPs) two times from the stack to get the two operands it needs (Operand), and then presses its own calculation result into the calculation stack when the calculation is complete, and the element at the top of the stack is the cumulative result (that is, integer 300);
    • The stloc.0 of line 4th is the instruction to set the local variable, which pops (POPs) an element from the compute stack and then saves the element to a specific local variable (this example is the first local variable). Note: Local variables must be pre-declared by the method.

Two, basically the assembly language or similar IL such intermediate instruction set does not have in the high-level language the natural if/else, Switch/case, Do/while, for/foreach such basic language structure, they only resemble goto/jump/br such as unconditional jump and br.true/br.false/beq/blt/bgt/ceq/clt/cgt such as conditional jump instructions, high-level language, many of the basic language structure is converted from the compiler or interpreter to the underlying jump structure, so in emitting We also need the brain to complement the compiler in such a translation mechanism, those if/else, while, for and other translation into the corresponding jump structure.

It is important to note that because of the "short-circuit" built-in conventions in the logical operations of high-level languages such as C/c++/c#/java, it is important to be aware of dealing with this problem when converting to a jump structure, otherwise breaking semantics and possibly causing runtime errors.

Third, because IL supports class names, fields, properties, methods, and other element names that contain characters other than letters, numbers, and underscores, all high-level language compilers take advantage of this feature, primarily to avoid naming conflicts with user code in a particular high-level language, which we also adopt.

With the above basic knowledge, I take a little time to read some IL code, and then to flip through the Zongsoft.Data.Entity class source is simple. In addition, when the anti-compilation read IL code, if you decompile the Debug version, you will find that the generated IL on the local variable processing is very verbose, repeated saving and immediately after loading local variables, this is because the compiler did not optimize the cause, do not worry, with Release compiled is much better, However, there are still some manually optimized spaces.

Interface description

The source code for the entity dynamic Generator class is located in the Zongsoft.corelibrary project (https://github.com/Zongsoft/Zongsoft.CoreLibrary/blob/feature-data/src/ Data/entity.cs), which is a static class whose main public method is defined as follows:

public static Entity{    public static T Build<T>();    public static T Build<T>(Action<T> map);    public static IEnumerable<T> Build<T>(int count, Action<T, int> map = null);    public static object Build(Type type);    public static object Build(Type type, Action<object> map);    public static IEnumerable Build(Type type, int count, Action<object, int> map = null);}

The public Save() method is a method for debugging, which saves the dynamically compiled assembly to a file so that it can be ilspy with a tool such as feature-data, which is removed after the merge into the Master branch.

About running points

In https://github.com/Zongsoft/Zongsoft.CoreLibrary/blob/feature-data/samples/Zongsoft.Samples.Entities/ The Program.cs class PerformanceDynamic(int count) is a dynamically generated run (performance test) code, it should be noted that if it is the first time to dynamically create an entity interface, the internal will be dynamic compilation first.

The following two ways to run the test mode will have different performance, we first pondering the reasons for the next reading.

private static void performancedynamic (int count) {   //gets the build delegate, which may trigger internal precompilation (that is, preheat)    var creator = Dat A.entity.getcreator (typeof (Models.iuserentity));    //Create run sub-timer    var stopwatch = new Stopwatch ();    stopwatch. Start (); Start timing    /* First run */   for (int i = 0; i < count; i++)    {       / /Call the build delegate to create the entity class instance        var user = (models.iuserentity) creator ();        user. UserId = (UINT) i;        user. Avatar = ": Smile:";        user. Name = "Name:" + i.tostring ();        user. FullName = "FullName";        user. Namespace = "Zongsoft";        user. Status = (byte) (i% byte). MaxValue);        user. Statustimestamp = (i% 11 = = 0)? DateTime.Now:DateTime.MinValue;        user. Createdtime = DateTime.Now;   &NBSP;}    stopwatch. Restart (); Re-chronograph    /* Second run */   int index = 0;    //Dynamic Build Specifies Count entity class instances (lazy build)    var entities = data.entity.build<models.iuserentity> (count) ;    foreach (var user in entities)    {       user. UserId = (UINT) index;        user. Avatar = ": Smile:";        user. Name = "Name:" + index. ToString ();        user. FullName = "FullName";        user. Namespace = "Zongsoft";        user. Status = (byte) (index% byte.) MaxValue);        user. Statustimestamp = (index++% 11 = = 0)? DateTime.Now:DateTime.MinValue;        user. Createdtime = DateTime.Now;    }    stopwatch. Stop (); Stop Timer}

Running on my old desktop 1 million (that is, count=1,000,000) times, the second run sub-code is almost slower than the first one 50~100 milliseconds, the difference between the for loop and the Enumerable/Enumerator pattern, I tried to Build<T>(int count) approach the internal yield return (by C # The compiler translates the statement into a Enumerable/Enumerator pattern) instead of the manual implementation, the idea of optimization is: Because in this scenario, we know the count number, based on this requirement can be eliminated Enumerator in the loop some unnecessary conditional judgment code. However, after writing it manually, it Enumerable/Enumerator was found that some necessary conditional judgments could not be omitted for code security, as it was not possible to determine whether the user would use entities. GetEnumerator () + while, which means that even count under certain conditions, it does not make any performance cheaper, after all, basic code security is a priority.

As mentioned above, the dynamically generated code does not have a performance problem, but in the case of a one-time creation of millions of entity instances and traverse the scene, in order to exclude the Enumerable/Enumerator mode of performance of a little "interference" ( which is necessary ) to take a bit of optimization, in the actual business is usually not required to do so, It is hereby stated.

Instructions for use

Change the various entity classes in the original business system to interfaces, which can inherit from Zongsoft.Data.IEntity or not, regardless of whether the entity interface inherits from the Zongsoft.Data.IEntity interface, the dynamically generated entity class implements the interface, and the dynamically created entity instance can still be cast to that interface.

Note: The entity interface cannot contain events, method definitions, or only property definitions.

Change notification

If an entity needs to support property change notifications, the entity interface must increase the inheritance of the System.ComponentModel.INotifyPropertyChanged interface, but such support requires a little performance cost, following the dynamic generation of some C # code.

public interface IPerson{    string Name { get; set; }}// 不支持的属性变更通知版本public class Person : IPerson, IEntity{    public string Name    {        get => _name;        set => {            _name = value;            _MASK_ |= 1;        }    }}/* 增加对属性变更通知的特性 */public interface IPerson : INotifyPropertyChanged{    string Name { get; set; }}// 支持属性变更通知版本public class Person : IPerson, IEntity, INotifyPropertyChanged{    // 事件声明    public event PropertyChangedEventHandler PropertyChanged;    public string Name    {        get => _name;        set => {            if(_name == value)  // 新旧值比对判断                return;            _name = value;            _MASK_ |= 1;            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));        }    }}

The so-called little performance cost has two points: ① need to compare the old and new values, compared to the implementation of the method of performance on the impact of this, ② the validity of the PropertyChanged event to judge and invoke the event delegate. Of course, if this is a necessary feature demand, then the cost is irrelevant.

tip: for a description of the new and old value comparison, if the property type is a primitive type, the dynamic generator generates a specific IL directive such as bne/be, otherwise the implementation of the operator is used if the type overrides the = = operator; otherwise the object.equals (...) is called. The static method to compare.

Extended Properties

In some scenarios where you need to handle the property's getter or setter's business logic manually, how do you implant these logic code in a dynamic build? Zongsoft.Data.EntityThere is a PropertyAttribute custom attribute class in the class that you can use to declare an implementation of an extended property. such as the following example:

public static UserExtension{    public static string GetAvatarUrl(IUser user)    {        if(string.IsNullOrEmpty(user.Avatar))            return null;        return "URL:" + user.Avatar;    }}public interface IUser{    string Avatar { get; set; }    [Entity.Property(Entity.PropertyImplementationMode.Extension, typeof(UserExtension))]    string AvatarUrl { get; }}/*  以下的 User 实体类为动态生成器生成的部分示意代码。*/public class User : IUser, IEntity{    private string _avatar;    public string Avatar    {        get => _avatar;        set {            _avatar = value;            _MASK_ |= xxx;        }    }    public string AvatarUrl    {        get {            return UserExtension.GetAvatarUrl(this);        }    }}

The above code is better understood, not to say, if the IUser interface is a AvatarUrl read-write property or a System.ComponentModel.DefaultValueAttribute custom attribute adornment, then the property will have a corresponding field, the corresponding property extension method can also get the field value.

public static class UserExtension{    public static string GetAvatarUrl(IUser user, string value)    {        if(string.IsNullOrEmpty(value))            return $"http://...{user.Avatar}...";        return value;    }}public interface IUser{    string Avatar { get; set; }    [Entity.Property(Entity.PropertyImplementationMode.Extension, typeof(UserExtension))]    string AvatarUrl { get; set; }}/*  以下的 User 实体类为动态生成器生成的部分示意代码。*/public class User : IUser, IEntity{    private string _avatar;    private string _avatarUrl;    public string Avatar    {        get => _avatar;        set {            _avatar = value;            _MASK_ |= xxx;        }    }    // 只有读取获取扩展方法    public string AvatarUrl    {        get => Extension.GetAvatarUrl(this, _avatarUrl);        set {            _avatarUrl = value;            _MASK_ |= xxx;        }    }}

Of course, the extended attribute method supports both read and write, and the following is how the two-version extension method is implemented:

public static class UserExtension{    public static string GetAvatarUrl(IUser user, string value)    {        throw new NotImplementedException();    }    public static bool SetAvatarUrl(IUser user, string value)    {        throw new NotImplementedException();    }}/*  以下的 User 实体类为动态生成器生成的部分示意代码。*/public class User : IUser, IEntity{    public string AvatarUrl    {        get => UserExtension.GetAvatarUrl(this, _avatarUrl);        set {            if(UserExtension.SetAvatarUrl(this, _avatarUrl))            {                _avatarUrl = value;                _MASK_ |= xxx;            }        }    }}

Definition conventions for extended property methods:

    1. Must be a public static method;
    2. The Read method name starts with Get, followed by the extended property name and is case-sensitive;
    3. The first parameter of the Read method must be to extend the Entity interface type, the second parameter is optional, if any, the type of the extended property, and the return type must be the type of the extended property;
    4. The Set method name starts with set, followed by the extended property name and is case-sensitive;
    5. The first parameter of the set method must be to extend the Entity interface type, the second parameter is the type of the extended property, the new value of the setting, the return type must be a Boolean type, return True (True) indicates that the setting succeeds or the return fails (False), and only the member fields that return true correspond are set to be updated.

Single-Case mode

In some scenarios, attributes need to be implemented in a singleton pattern, such as the properties of some collection types.

public interface IDepartment{    [Entity.Property(Entity.PropertyImplementationMode.Singleton)]    ICollection<IUser> Users { get; }}/*  以下的 Department 实体类为动态生成器生成的部分示意代码。*/public class Department : IDepartment, IEntity{    private readonly object _users_LOCK;    private ICollection<IUser> _users;    public Department()    {        _users_LOCK = new object();    }    public ICollection<IUser> Users    {        get {            if(_users == null) {                lock(_users_LOCK) {                    if(_users == null) {                        _users = new List<IUser>();                    }                }            }            return _users;        }    }}

The implementation is in double-lock mode, and it must be noted that each singleton attribute will occupy an additional type variable for double-check locks object .
If the property type is a collection interface , then the dynamic generator chooses an appropriate collection class that implements the interface; Of course, you can also customize a factory method to create the corresponding instance, in the entity properties PropertyAttribute The custom attribute declares the type of the factory method.

Note: The factory method must be a public static method with an optional parameter with the type of the entity interface.

public static class DepartmentExtension{    public static ICollection<IUser> GetUsers(IDepartment department)    {        return new MyUserCollection(department);    }}public interface IDepartment{    [Entity.Property(Entity.PropertyImplementationMode.Singleton, typeof(DepartmentExtension))]    ICollection<IUser> Users { get; }}/*  以下的 Department 实体类为动态生成器生成的部分示意代码。*/public class Department : IDepartment, IEntity{    private readonly object _users_LOCK;    private ICollection<IUser> _users;    public Department()    {        _users_LOCK = new object();    }    public ICollection<IUser> Users    {        get {            if(_users == null) {                lock(_users_LOCK) {                    if(_users == null) {                        _users = DepartmentExtension.GetUsers(this);                    }                }            }            return _users;        }    }}

Default values and custom initialization

Sometimes we need a read-only attribute, but we do not need a relatively heavy implementation of Singleton mode, which can be DefaultValueAttribute handled by this custom attribute.

Tip: All custom attributes of an entity interface or property declaration are added to the corresponding elements of the entity class by the generator, and the following presentation code may omit these generated custom attributes, as described.

public interface IDepartment{    [DefaultValue("Popeye")]    string Name { get; set; }    [DefaultValue]    ICollection<IUser> Users { get; }}/*  以下的 Department 实体类为动态生成器生成的部分示意代码。*/public class Department : IDepartment, IEntity{    private string _name;    private ICollection<IUser> _users;    public Department()    {        _name = "Popeye";        _users = new List<IUser>();    }    [DefaultValue("Popeye")]    public string Name    {        get => _name;        set {            _name = value;            _MASK_ |= xxx;        }    }    [DefaultValue()]    public ICollection<IUser> Users    {        get => _users;    }}

In addition to supporting the fixed (Mutable) default value, also supports dynamic (immutable), so-called dynamic value refers to its value is not DefaultValueAttribute cured, that is, the specified DefaultValueAttribute value is a type of a static class, the static class must have a named Get preceded by the property name of the method, The method can have no parameters, or it can have a parameter of the entity interface type, as shown below.

public static DepartmentExtension{    public static DateTime GetCreationDate()    {        return DateTime.Now;    }}public interface IDepartment{    [DefaultValue(typeof(DepartmentExtension))]    DateTime CreationDate { get; }}/*  以下的 Department 实体类为动态生成器生成的部分示意代码。*/public class Department : IDepartment, IEntity{    private DateTime _creationDate;    public Department()    {        _creationDate = DepartmentExtension.GetCreationDate();    }    public DateTime CreationDate    {        get => _creationDate;    }}

If DefaultValueAttribute a type is specified in the default custom attribute (that is System.Type ), and the type is not a type of a static class, and the property type is not System.Type , it means that the type is the actual type of the property, which is particularly useful in cases where some properties are declared as interfaces or base classes. as shown below.

public interface IDepartment{    [DefaultValue(typeof(MyManager))]    IUser Manager { get; set; }    [DefaultValue(typeof(MyUserCollection))]    ICollection<IUser> Users { get; }}/*  以下的 Department 实体类为动态生成器生成的部分示意代码。*/public class Department : IDepartment, IEntity{    private IUser _manager;    private ICollection<IUser> _users;    public Department()    {        _managert = new MyManager();        _users = new MyUserCollection();    }    public IUser Manager    {        get => _manager;        set => _manager = value;    }    public ICollection<IUser> Users    {        get => _users;    }}

Other Notes

The default generated entity properties are public properties (that is, non-explicit implementations), and Entity.PropertyAttribute explicit implementation mechanisms can be turned on by customizing attributes when an entity interface has a duplicate attribute name in the inheritance, or because some special requirements cause it to be explicitly implemented for an entity attribute IsExplicitImplementation=true .

A variety of custom attributes (Attribute) declared in the entity interface are added to the generated entity class as-is by the dynamic generator. As a result, the various custom attributes (including:,) of the interface and the interface's property declarations are DefaultValueAttribute Entity.PropertyAttribute added to the corresponding elements of the dynamically generated entity class, which is a feature that must be supported for some applications.

Performance testing

In the dynamic generation of entity classes (ii), we have verified the execution performance of the design, but with the details of the features described above, it is also necessary to remind that: because of the open DefaultValueAttribute , extended attribute methods, Singleton properties, property change notification will result in the generated code and the most basic field access to enhance the functionality , corresponding to the number of code to run increased, so the running points have an impact, but this effect is determined to know, they are not the implementation of feature requirements, algorithm defects caused, please understand.

Pedagogical gardening second is to increase the performance impact of property change notifications (that is, entity interface Inheritance INotifyPropertyChanged ), which is the row of Dynamic entity.

Written in the last words

This entity class Dynamic generator is easy to use, run performance and memory utilization are very good (including the Ientiy interface to provide a great feature), will become one of the future of all of our business system infrastructure, so the subsequent articles (if there are also) should be seen frequently in its application.

Counted down spent a full three days (day and Night are written) to complete the "dynamic generation of entity class" series of articles, really feel that writing articles than writing code is also tired, and this is still omitted should be equipped with some flowchart, the case of architectural diagram. Plan Next I will for the Zongsoft (https://github.com/Zongsoft) series Open source project to write all the documents, according to this writing, the bottom of my heart not by the rise of a hint of fear and faint sadness.

Finally, because writing this thing delayed a lot of time to build zongsoft.data this wheel, so we have to go to build the wheel. Plan to have at least one dry-filled technical article in the public number, hoping not to disappoint.

About Zongsoft.data It must be a full-blooded, easy-to-use, flexible data engine that will support four relational databases and will be added to the Elasticsearch support, in short, it should be different from any ORM on the market Open source products for the data engine. I will continue to share with you some of its design considerations and the problems encountered in the implementation, of course, you can also watch my progress on GitHub.

If you think this article is helpful to you, or if you think our open source project is doing well, please be sure to praise us and follow our public number, which is probably the biggest source of motivation for me to keep writing.

Tips

This article may be updated, please read the original text: Https://zongsoft.github.io/blog/zh-cn/zongsoft/entity-dynamic-generation-3 to avoid errors caused by outdated content, There is also a better reading experience.

This work is licensed using the   Creative Commons Attribution-NonCommercial use-Share 4.0 International license agreement in the same way. You are welcome to reprint, use, republish, but you must retain the Attribution Zhong Feng (including Link: http://zongsoft.github.io) of this article, and shall not be used for commercial purposes, and should be published with the same license based on the modified works of this article. If you have any questions or authorization, please write to me (zongsoft@qq.com).

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.