当前位置:网站首页>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
边栏推荐
- Browser - learning notes
- 三十.什么是vm和vc?
- Unity animation creates sequence frame code and generates animationclip
- Devaxpress report replay: complete the drawing of conventional two-dimensional report + histogram + pie chart
- [PTA] l1-006 continuity factor
- Leetcode 542, 01 matrix
- [graph theory brush question-5] Li Kou 1971 Find out if there is a path in the graph
- I JS deep copy and shallow copy
- Unity Odin ProgressBar add value column
- Identifier CV is not defined in opencv4_ CAP_ PROP_ FPS; CV_ CAP_ PROP_ FRAME_ COUNT; CV_ CAP_ PROP_ POS_ Frames problem
猜你喜欢
LeetCode 116. 填充每个节点的下一个右侧节点指针
vulnhub DC:1渗透笔记
[graph theory brush question-5] Li Kou 1971 Find out if there is a path in the graph
Rt-1052 learning notes - GPIO architecture analysis
Installation and use of NVM
Development of Matlab GUI bridge auxiliary Designer (functional introduction)
高薪程序员&面试题精讲系列91之Limit 20000加载很慢怎么解决?如何定位慢SQL?
A useless confession artifact
Plato farm is one of the four largest online IEOS in metauniverse, and the transaction on the chain is quite high
After route link navigation, the sub page does not display the navigation style problem
随机推荐
CONDA environment management command
[PTA] l1-006 continuity factor
[stack and queue topics] - sliding window
上海回應“面粉官網是非法網站”:疏於運維被“黑”,警方已立案
How to configure SSH public key in code cloud
The ODB model calculates the data and outputs it to excel
Awk print special characters
Experience of mathematical modeling in 18 year research competition
Development of Matlab GUI bridge auxiliary Designer (functional introduction)
LeetCode 542、01 矩阵
Matlab analytic hierarchy process to quickly calculate the weight
LeetCode 1346、检查整数及其两倍数是否存在
Solve the Chinese garbled code of URL in JS - decoding
黑客的入侵方式你知道几种?
三十一. `prototype`显示原型属性和`__proto__`隐式原型属性
LeetCode 74、搜索二维矩阵
Solution: NPM err! code ELIFECYCLE npm ERR! errno 1
Es keyword sorting error reason = fielddata is disabled on text fields by default Set fielddata = true on keyword in order
Es error: request contains unrecognized parameter [ignore_throttled]
2022dasctf APR x fat epidemic prevention challenge crypto easy_ real