当前位置:网站首页>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
边栏推荐
- Awk print special characters
- 缓存淘汰算法初步认识(LRU和LFU)
- 2021-09-02 unity project uses rider to build hot change project failure record of ilruntime
- 打新债中签以后怎么办,网上开户安全吗
- Analysis of the relationship between generalized Bim and CAD under the current background
- A useless confession artifact
- Why does ES6 need to introduce map when JS already has object type
- Parsing methods of JSON data in C - jar and jobobject: error reading jar from jsonreader Current JsonReader item
- "Meta function" of tidb 6.0: what is placement rules in SQL?
- Recommend an open source free drawing software draw IO exportable vector graph
猜你喜欢

Syntax Error: TypeError: this. getOptions is not a function

Modeling based on catiav6
ArcGIS JS version military landmark drawing (dovetail arrow, pincer arrow, assembly area) fan and other custom graphics

Unity animation creates sequence frame code and generates animationclip

"Meta function" of tidb 6.0: what is placement rules in SQL?
![[graph theory brush question-5] Li Kou 1971 Find out if there is a path in the graph](/img/fb/9822cccde4ca39d8066024c09a7349.png)
[graph theory brush question-5] Li Kou 1971 Find out if there is a path in the graph

The ODB model calculates the data and outputs it to excel

Unity solves Z-fighting

On BIM data redundancy theory

JS arrow function user and processing method of converting arrow function into ordinary function
随机推荐
Historical track data reading of Holux m1200-e Bluetooth GPS track recorder
How to configure SSH public key in code cloud
BMP JPEG 图片转换为矢量图像 ContourTrace
High paid programmer & interview question series 91 limit 20000 loading is very slow. How to solve it? How to locate slow SQL?
Go zero framework database avoidance Guide
Leetcode 74. Search two-dimensional matrix
Parsing methods of JSON data in C - jar and jobobject: error reading jar from jsonreader Current JsonReader item
Easy to use nprogress progress bar
Imitation Baidu map realizes the three buttons to switch the map mode by automatically shrinking the bottom
LeetCode 1337、矩阵中战斗力最弱的 K 行
The iswow64process function determines the number of program bits
bounding box iou
Recommend an open source free drawing software draw IO exportable vector graph
6-5 字符串 - 2. 字符串复制(赋值) (10 分)C语言标准函数库中包括 strcpy 函数,用于字符串复制(赋值)。作为练习,我们自己编写一个功能与之相同的函数。
【PTA】L1-002 打印沙漏
[graph theory brush question-4] force deduction 778 Swimming in a rising pool
Scripy tutorial - (2) write a simple crawler
Automatically fill in body temperature and win10 task plan
[SQL] string series 2: split a string into multiple lines according to specific characters
I JS deep copy and shallow copy