当前位置:网站首页>C knowledge

C knowledge

2022-04-23 20:45:00 Wind god Shura envoy

C# Use Emit A deep clone

Someone asked , How many methods are there to copy all the properties of one class to another class ? This is to ask how many methods deep cloning has , It's easy to think of three . Direct copy , Reflection replication , Serialization replication . But the faster ones are Expression tree copy IL Copy Two , This article mainly talks about the last

About expression tree replication , See Fast Deep Copy by Expression Trees (C#) - CodeProject

You need to know a little first IL Of , It's easier to say later , Suppose you know IL What is it? , It's easy to know IL How to write , Then start developing functions . The first step is to name , Because you need to copy all the properties of one class to another class , Need to call method , And the method needs a name , So the first step is to name .

To create a method public void Clone<T>(T source, T los) I used the following code

var dynamicMethod = new DynamicMethod("Clone", null, new[] {
     typeof(T), typeof(T) });

The first parameter to create the method is easy to see , I won't explain , The second parameter is the return value of the method , Because the return is void So you don't have to write . The third parameter is the parameter of the function , Just use the type , If there are multiple parameters, it is to write an array , If you find something here that you don't understand , Please tell me .

But after defining the method, you need to write the code inside the method , You need to use ILGenerator , Use his Emit Method , This method is very fast , You need to know when using IL Of , If you don't know , No problem , I'll be careful next .

ILGenerator generator = dynamicMethod.GetILGenerator();

You need to get all the properties of the type , Although reflection is used here , But only once , Because the reflection method here is to write IL Code , It can be used many times after writing , Maybe the speed is not fast for the first time , But then the speed is about the same as that of writing and compiling your own code , So it is recommended to use this method . You can use it yourself dot trace Go check the performance , What I see is that the performance is very good .

Take out the code that all attributes can read and write foreach (var temp in typeof(T).GetProperties().Where(temp=>temp.CanRead&&temp.CanWrite))

see IL You need to put the first parameter on the left first , The second parameter is placed on the right , Call the... Of the second parameter get Set the value of the first parameter set The normal code of the corresponding attribute looks like

los.foo=source.foo;

there foo Just get an attribute , It's random , Write out the IL Please see the following .

Ldarg_1 //los
Ldarg_0 //s
callvirt     instance string lindexi.Foo::get_Name()
callvirt     instance void lindexi.Foo::set_Name(string)
ret     

From the code above callvirt Use a method , Corresponding press in parameters , So you can get the method by reflection , Then call this method , So write the code, see below

                generator.Emit(OpCodes.Ldarg_1);// los
                generator.Emit(OpCodes.Ldarg_0);// s
                generator.Emit(OpCodes.Callvirt,temp.GetMethod);
                generator.Emit(OpCodes.Callvirt, temp.SetMethod);

Because you can take this out of the transformation method , So here's all the code

        private static void CloneObjectWithIL<T>(T source, T los)
        {
    
            var dynamicMethod = new DynamicMethod("Clone", null, new[] {
     typeof(T), typeof(T) });
            ILGenerator generator = dynamicMethod.GetILGenerator();
            foreach (var temp in typeof(T).GetProperties().Where(temp=>temp.CanRead&&temp.CanWrite))
            {
    
                generator.Emit(OpCodes.Ldarg_1);// los
                generator.Emit(OpCodes.Ldarg_0);// s
                generator.Emit(OpCodes.Callvirt,temp.GetMethod);
                generator.Emit(OpCodes.Callvirt, temp.SetMethod);
            }
            generator.Emit(OpCodes.Ret);
            var clone = (Action<T, T>) dynamicMethod.CreateDelegate(typeof(Action<T, T>));
            clone(source, los);
        }

If you test this method , Then you will find , This method will appear for classes that are invisible to this method MethodAccessException, So the incoming class needs this method and can directly use .

//A.dll
public class Foo
{
    
}
CloneObjectWithIL(foo1,foo2);
//B.dll
        private static void CloneObjectWithIL<T>(T source, T los)
 At this time... Cannot be used 

outside , For static properties , Using the above code will also make an error , Because the access of static properties does not have permission , So look at the revised .

    /// <summary>
    ///  Provides fast object deep replication 
    /// </summary>
    public static class Clone
    {
    
        /// <summary>
        ///  Provide use  IL  Fast object deep copy 
        ///  This method is required to have T Accessible 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"> Source </param>
        /// <param name="los"> Copy properties from source </param>
        /// <exception cref="MethodAccessException"> If you enter T There is no way to access , Then this exception will appear </exception>
        // ReSharper disable once InconsistentNaming
        public static void CloneObjectWithIL<T>(T source, T los)
        {
    
            // See  http://lindexi.oschina.io/lindexi/post/C-%E4%BD%BF%E7%94%A8Emit%E6%B7%B1%E5%85%8B%E9%9A%86/
            if (CachedIl.ContainsKey(typeof(T)))
            {
    
                ((Action<T, T>) CachedIl[typeof(T)])(source, los);
                return;
            }
            var dynamicMethod = new DynamicMethod("Clone", null, new[] {
     typeof(T), typeof(T) });
            ILGenerator generator = dynamicMethod.GetILGenerator();
            foreach (var temp in typeof(T).GetProperties().Where(temp => temp.CanRead && temp.CanWrite))
            {
    
                // Do not copy static class properties 
                if (temp.GetAccessors(true)[0].IsStatic)
                {
    
                    continue;
                }
                
                generator.Emit(OpCodes.Ldarg_1);// los
                generator.Emit(OpCodes.Ldarg_0);// s
                generator.Emit(OpCodes.Callvirt, temp.GetMethod);
                generator.Emit(OpCodes.Callvirt, temp.SetMethod);
            }
            generator.Emit(OpCodes.Ret);
            var clone = (Action<T, T>) dynamicMethod.CreateDelegate(typeof(Action<T, T>));
            CachedIl[typeof(T)] = clone;
            clone(source, los);
        }
        private static Dictionary<Type, Delegate> CachedIl {
     set; get; } = new Dictionary<Type, Delegate>();
    }

We need to pay attention to , The copy here just copies the properties of the class , The properties of the class are not copied . If there is a type TestA1 , Look at the following code .

        public class TestA1
        {
    
            public string Name {
     get; set; }
        }

So after executing the following code , Got TestA1 It's the same .

        public class Foo
        {
    
            public string Name {
     get; set; }
         
            public TestA1 TestA1 {
     get; set; }
        }
             var foo = new Foo()
            {
    
                Name = "123",
                TestA1 = new TestA1()
                {
    
                    Name = "123"
                }
            };
            var foo1 = new Foo();
            Clone.CloneObjectWithIL(foo, foo1);
            foo1.TestA1.Name == foo.TestA1.Name
            foo.Name = "";
            foo.TestA1.Name = "lindexi";
            foo1.TestA1.Name == foo.TestA1.Name

So when can the above code be used ? In fact, if you need to copy the properties of the base class in a created class , So it's good to use this method , For example, in Model Will create some classes , And in the ViewModel Sometimes you need to add some properties to these classes , Such as Checked , Then you need to replicate Model Properties of , If you need to write your own attributes, copy them , So the development speed is too slow . So you can use this method at this time .

For example, the base class is Base , The inheritance class is Derived , Look at the following code

public class Base
{
    
    public string BaseField;
}
public class Derived : Base
{
    
    public string DerivedField;
}
Base base = new Base();
//some alother code
Derived derived = new Derived();
CloneObjectWithIL(base, derived);

If you need to copy a class to a new class , You can use this code

    private static T CloneObjectWithIL<T>(T myObject)
    {
    
        Delegate myExec = null;
        if (!_cachedIL.TryGetValue(typeof(T), out myExec))
        {
    
            // Create ILGenerator
            DynamicMethod dymMethod = new DynamicMethod("DoClone", typeof(T), new Type[] {
     typeof(T) }, true);
            ConstructorInfo cInfo = myObject.GetType().GetConstructor(new Type[] {
     });
            ILGenerator generator = dymMethod.GetILGenerator();
            LocalBuilder lbf = generator.DeclareLocal(typeof(T));
            //lbf.SetLocalSymInfo("_temp");
            generator.Emit(OpCodes.Newobj, cInfo);
            generator.Emit(OpCodes.Stloc_0);
            foreach (FieldInfo field in myObject.GetType().GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic))
            {
    
                // Load the new object on the eval stack... (currently 1 item on eval stack)
                generator.Emit(OpCodes.Ldloc_0);
                // Load initial object (parameter) (currently 2 items on eval stack)
                generator.Emit(OpCodes.Ldarg_0);
                // Replace value by field value (still currently 2 items on eval stack)
                generator.Emit(OpCodes.Ldfld, field);
                // Store the value of the top on the eval stack into the object underneath that value on the value stack.
                // (0 items on eval stack)
                generator.Emit(OpCodes.Stfld, field);
            }
            
            // Load new constructed obj on eval stack -> 1 item on stack
            generator.Emit(OpCodes.Ldloc_0);
            // Return constructed object. --> 0 items on stack
            generator.Emit(OpCodes.Ret);
            myExec = dymMethod.CreateDelegate(typeof(Func<T, T>));
            _cachedIL.Add(typeof(T), myExec);
        }
        return ((Func<T, T>)myExec)(myObject);
    }

C# Wildcard employment confirmation rule

You can use the following code to convert a wildcard to a regular string

    public static class WildcardRegexString
    {
    
        /// <summary>
        ///  Wildcard employment confirmation rule 
        /// </summary>
        /// <param name="wildcardStr"></param>
        /// <returns></returns>
        public static string GetWildcardRegexString(string wildcardStr)
        {
    
            Regex replace = new Regex("[.$^{\\[(|)*+?\\\\]");
            return replace.Replace(wildcardStr,
                       delegate (Match m)
                       {
    
                           switch (m.Value)
                           {
    
                               case "?":
                                   return ".?";
                               case "*":
                                   return ".*";
                               default:
                                   return "\\" + m.Value;
                           }
                       }) + "$";
        }
    }

Files are often case insensitive , So you need to write a function to tell the user , No case sensitivity is required .

        /// <summary>
        ///  Get the regular name of the wildcard 
        /// </summary>
        /// <param name="wildcarStr"></param>
        /// <param name="ignoreCase"> Ignore case </param>
        /// <returns></returns>
        public static Regex GetWildcardRegex(string wildcarStr, bool ignoreCase)
        {
    
            if (ignoreCase)
            {
    
                return new Regex(GetWildcardRegexString(wildcarStr));
            }
            return new Regex(GetWildcardRegexString(wildcarStr), RegexOptions.IgnoreCase);
        }

Regular can use assembly mode , Slow start , But it's fast

private static Regex _regex = new Regex("[.$^{\\[(|)*+?\\\\]", RegexOptions.Compiled);

My software needs to be reused , So use this .


C# Enumeration to string

Sometimes you need to convert an enumeration to a string , So how to convert enumeration to string ?

Enumeration to string

If you need to convert enumeration to string , You can convert him directly , Please look at the code.

        public enum Di
        {
    
            /// <summary>
            ///  orbital 
            /// </summary>
            Railway,
            /// <summary>
            ///  The river 
            /// </summary>
            River,
        }
        static void Main(string[] args)
        {
    
            Console.WriteLine(Di.Railway.ToString());
        }

In this way, you can convert the enumeration to a string

Except for this method , have access to C# 6.0 Key words of , Please look at the code.

Console.WriteLine(nameof(Di.Railway));

String to enumeration

If you convert an enumeration to a string , So how to convert a string to an enumeration ? have access to Enum.Parse However, this method can throw exceptions , So you need to know that the string can be converted to

  public enum Di
        {
    
            /// <summary>
            ///  orbital 
            /// </summary>
            Railway,
            /// <summary>
            ///  The river 
            /// </summary>
            River,
        }
   static void Main(string[] args)
        {
    
            string str = Di.Railway.ToString();
            Console.WriteLine(Enum.Parse(typeof(Di), str).ToString());
        }

If for an uncertain string , Include empty values , May adopt TryParse Method

            if (Enum.TryParse(typeof(Di),null,out var value))
            {
    
                
            }

The above code will only return false There will be no prompt that cannot be converted


C# Find vs FirstOrDefault

This article tells you , When getting the first element of the array , Which method performs better .

Need to know , Both methods are Linq Methods , Before use, you need to reference Linq . about List And so on are inherited enumerable Enumerable At this point, to get the first element, you can use FirstOrDefault. If you use Find Then the type of array required is IList.

Let's write a simple example

Decompile Find You can see the following code , The following code deletes some code , Make it easier for everyone to see Find It uses for Then use judgment

private T[] _items;
public T Find(Predicate<T> match)
{
    
  for (int index = 0; index < this._size; ++index)
  {
    
    if (match(this._items[index]))
      return this._items[index];
  }
  return default (T);
}

and FirstOrDefault Your code does not exist foreach , This will call the... Of the list GetEnumerator Method , And it will call... At the end Dispose . such FirstOrDefault Is better than Find A little bit worse .

public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    
  foreach (TSource source1 in source)
  {
    
    if (predicate(source1))
      return source1;
  }
  return default (TSource);
}

So for List Type to get the first or default, please use Find , For others, please use FirstOrDefault

  • about List , Use for The speed of foreach Twice as many
  • Traverse array The speed is traversal List Twice as many
  • Use for Traverse array The speed is using foreach Traverse List Of 5 times

版权声明
本文为[Wind god Shura envoy]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/04/202204232039329901.html