当前位置:网站首页>C# 知识
C# 知识
2022-04-23 20:39:00 【风神修罗使】
C# 使用Emit深克隆
有人问,复制一个类所有属性到另一个类有多少方法?这也就是问深克隆有多少个方法,容易想的有三个。直接复制,反射复制,序列化复制。但是性能比较快的有 表达式树复制 IL复制 两个,本文主要讲最后一个
关于表达式树复制,参见 Fast Deep Copy by Expression Trees (C#) - CodeProject
需要先知道一点IL的,后面才比较容易说,假设大家知道了 IL 是什么, 知道了简单的 IL 如何写,那么开始进行功能的开发。第一步是命名,因为需要把一个类的所有属性复制到另一个类,需要调用方法,而方法需要名字,所以第一步就是命名。
为了创建方法 public void Clone<T>(T source, T los) 我就使用了下面代码
var dynamicMethod = new DynamicMethod("Clone", null, new[] {
typeof(T), typeof(T) });
创建方法的第一个参数很容易看到,我就不解释了,第二个参数就是方法的返回值,因为返回是 void 所以不用写。第三个参数是函数的参数,只需要使用类型,如果有多个参数就是写数组,如果这里发现有看不懂的,请和我说。
但是定义方法后需要写方法内的代码,这时需要使用 ILGenerator ,使用他的 Emit 方法,这个方法的速度很快,使用的时候需要知道 IL 的,如果不知道,没关系,我接下来会仔细说。
ILGenerator generator = dynamicMethod.GetILGenerator();
需要获得类型的所有属性,虽然这里用了反射,但是只是用一次,因为这里用反射获得方法是在写IL代码,写完可以很多次使用,可能第一次的速度不快,但是之后的速度和自己写代码编译的速度是差不多,所以建议使用这个方法。可以自己去使用 dot trace 去查看性能,我自己看到的是性能很好。
拿出所有属性可以读写的代码foreach (var temp in typeof(T).GetProperties().Where(temp=>temp.CanRead&&temp.CanWrite))
查看 IL 需要先把第一个参数放在左边,第二个参数放在右边,调用第二个参数的 get 设置第一个参数的set对应的属性看起来的正常代码就是
los.foo=source.foo;
这里的 foo 就是拿到一个属性,随意写的,写出来的 IL 请看下面。
Ldarg_1 //los
Ldarg_0 //s
callvirt instance string lindexi.Foo::get_Name()
callvirt instance void lindexi.Foo::set_Name(string)
ret
可以从上面的代码 callvirt 使用一个方法,对应压入参数,所以可以通过反射获得方法,然后调用这个方法,于是写成代码请看下面
generator.Emit(OpCodes.Ldarg_1);// los
generator.Emit(OpCodes.Ldarg_0);// s
generator.Emit(OpCodes.Callvirt,temp.GetMethod);
generator.Emit(OpCodes.Callvirt, temp.SetMethod);
因为可以把这个拿出转化方法,于是所以的下面给所有代码
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);
}
如果测试了这个方法,那么会发现,这个方法对于这个方法不可以见的类就会出现MethodAccessException,所以传入的类需要这个方法可以直接用。
//A.dll
public class Foo
{
}
CloneObjectWithIL(foo1,foo2);
//B.dll
private static void CloneObjectWithIL<T>(T source, T los)
这时无法使用
之外,对于静态属性,使用上面代码也是会出错,因为静态的属性的访问没有权限,所以请看修改后的。
/// <summary>
/// 提供快速的对象深复制
/// </summary>
public static class Clone
{
/// <summary>
/// 提供使用 IL 的方式快速对象深复制
/// 要求本方法具有T可访问
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">源</param>
/// <param name="los">从源复制属性</param>
/// <exception cref="MethodAccessException">如果输入的T没有本方法可以访问,那么就会出现这个异常</exception>
// ReSharper disable once InconsistentNaming
public static void CloneObjectWithIL<T>(T source, T los)
{
//参见 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))
{
//不复制静态类属性
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>();
}
需要注意,这里的复制只是复制类的属性,对类的属性内是没有进行复制。如果存在类型 TestA1 ,请看下面代码。
public class TestA1
{
public string Name {
get; set; }
}
那么在执行下面的代码之后,得到的 TestA1 是相同的。
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
那么上面的代码在什么时候可以使用?实际如果在一个创建的类需要复制基类的属性,那么使用这个方法是很好,例如在 Model 会创建一些类,而在 ViewModel 有时候需要让这些类添加一些属性,如 Checked ,那么需要重新复制 Model 的属性,如果一个个需要自己写属性复制,那么开发速度太慢。所以这时候可以使用这个方法。
例如基类是 Base ,继承类是Derived ,请看下面代码
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);
如果需要复制一个类到一个新类,可以使用这个代码
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# 通配符转正则
可以使用下面代码把通配符转正则字符串
public static class WildcardRegexString
{
/// <summary>
/// 通配符转正则
/// </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;
}
}) + "$";
}
}
文件经常是不需要区分大小写,所以需要写一个函数告诉用户,不需要区分大小写。
/// <summary>
/// 获取通配符的正则
/// </summary>
/// <param name="wildcarStr"></param>
/// <param name="ignoreCase">是否忽略大小写</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);
}
正则可以使用程序集方式,启动慢,但是运行快
private static Regex _regex = new Regex("[.$^{\\[(|)*+?\\\\]", RegexOptions.Compiled);
我的软件就需要重复使用,于是就使用这个。
C# 枚举转字符串
有时候需要把枚举转字符串,那么如何把枚举转字符串?
枚举转字符串
假如需要把枚举转字符串,可以直接把他进行转换,请看代码
public enum Di
{
/// <summary>
/// 轨道
/// </summary>
Railway,
/// <summary>
/// 河流
/// </summary>
River,
}
static void Main(string[] args)
{
Console.WriteLine(Di.Railway.ToString());
}
这样就可以把枚举转字符串
除了这个方法,可以使用 C# 6.0 的关键字,请看代码
Console.WriteLine(nameof(Di.Railway));
字符串转枚举
如果把一个枚举转字符串,那么如何把字符串转枚举?可以使用 Enum.Parse 不过这个方法可以会抛异常,所以使用需要知道字符串是可以转
public enum Di
{
/// <summary>
/// 轨道
/// </summary>
Railway,
/// <summary>
/// 河流
/// </summary>
River,
}
static void Main(string[] args)
{
string str = Di.Railway.ToString();
Console.WriteLine(Enum.Parse(typeof(Di), str).ToString());
}
如果对于不确定的字符串,包括空的值,可以采用 TryParse 方法
if (Enum.TryParse(typeof(Di),null,out var value))
{
}
上面代码只会返回 false 不会提示无法转换
C# Find vs FirstOrDefault
本文告诉大家,在获得数组第一个元素时,使用哪个方法性能更高。
需要知道,两个方法都是 Linq 的方法,使用之前需要引用 Linq 。对于 List 等都是继承可枚举Enumerable这时获取第一个元素可以使用FirstOrDefault。如果使用Find那么需要数组的类型是IList。
下面写一个简单的例子
反编译 Find 可以看到下面代码,下面的代码删了一些代码,让大家比较容易看到 Find 使用的是 for 然后使用判断
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);
}
而 FirstOrDefault 的代码存在 foreach ,这会调用列表的 GetEnumerator 方法,而且还会在结束的时候调用 Dispose 。这样 FirstOrDefault 的性能就比 Find 稍微差一些。
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);
}
所以在对于 List 类型的获得第一个或默认请使用 Find ,其他的请使用FirstOrDefault
- 对于
List,使用for的速度是foreach的两倍 - 遍历
array的速度是遍历List的两倍 - 使用
for遍历array的速度是使用foreach遍历List的5倍
版权声明
本文为[风神修罗使]所创,转载请带上原文链接,感谢
https://blog.csdn.net/WuLex/article/details/123371743
边栏推荐
- 2021-09-02 unity project uses rider to build hot change project failure record of ilruntime
- go-zero框架数据库方面避坑指南
- Unity ECS dots notes
- Leetcode 1351. Negative numbers in statistical ordered matrices
- Commit and rollback in DCL of 16 MySQL
- Bracket matching -- [implementation of one-dimensional array]
- Monte Carlo py solves the area problem! (save pupils Series)
- JSX syntax rules
- Solve the Chinese garbled code of URL in JS - decoding
- C migration project record: modify namespace and folder name
猜你喜欢

Commande dos pour la pénétration de l'Intranet

How to use PM2 management application? Come in and see

Unity solves Z-fighting

Commit and ROLLBACK in DCL of 16mysql

堡垒机、跳板机JumpServer的搭建,以及使用,图文详细

Latest investigation and progress of building intelligence based on sati

Click an EL checkbox to select all questions

【栈和队列专题】—— 滑动窗口

缓存淘汰算法初步认识(LRU和LFU)

Leetcode 994, rotten orange
随机推荐
Shanghai responded that "flour official website is an illegal website": neglect of operation and maintenance has been "hacked", and the police have filed a case
16MySQL之DCL 中 COMMIT和ROllBACK
How to configure SSH public key in code cloud
Historical track data reading of Holux m1200-e Bluetooth GPS track recorder
Create vs project with MATLAB
Use of node template engine
SQL: query duplicate data and delete duplicate data
go slice
Mathematical modeling column | Part 5: MATLAB optimization model solving method (Part I): Standard Model
Solution: NPM err! code ELIFECYCLE npm ERR! errno 1
[PTA] get rid of singles
Development of Matlab GUI bridge auxiliary Designer (functional introduction)
Vulnhub DC: 1 penetration notes
Commande dos pour la pénétration de l'Intranet
Resolve the eslint warning -- ignore the warning that there is no space between the method name and ()
Research on open source OCR engine
Es error: request contains unrecognized parameter [ignore_throttled]
bounding box iou
堡垒机、跳板机JumpServer的搭建,以及使用,图文详细
An error occurs when the addressable assets system project is packaged. Runtimedata is null