深入理解 C# 反射 的使用
总目录
前言
反射是.NET框架中一个强大的特性,允许程序在运行时检查和操作类型信息。通过反射,开发者可以动态地创建对象、调用方法、访问属性等,为程序提供了极大的灵活性。本文将详细讲解C#反射的使用方法及其应用场景。
一、什么是反射?
1. 定义
- 反射(Reflection) 是指程序在运行时能够检查和操作其自身的类型信息。通过反射,可以获取类型的成员(如方法、属性、事件等)并动态地调用它们。
- 在.NET框架中,反射的主要实现位于
System.Reflection
命名空间中。在程序运行时通过 System.Reflection 命名空间提供的 API,可完成如下操作:- 获取类型信息:如类名、命名空间、继承关系、实现的接口等。
- 动态创建对象:无需显式实例化类型。
- 访问成员:包括私有字段、方法、属性等。
- 执行方法:通过方法名或参数动态调用。
反射,简单来说,就是程序在运行时能够自我检查,获取类型、方法、字段等元数据信息的能力。
2. 作用
- 动态类型操作:在运行时获取类型信息(元数据),并根据这些信息执行相应的操作(动态创建对象实例或执行方法)。
- 实现插件系统:通过反射加载外部程序集,实现可扩展的插件架构。
- 序列化/反序列化:获取对象的字段和属性,实现数据的序列化和反序列化。
- 自定义特性处理:读取类型或成员上的自定义特性,根据特性进行特定处理。
- 代码分析工具:开发调试器或分析工具时,反射可以帮助你获取程序内部的状态。
3. 反射相关类与命名空间
反射的核心功能依赖于 System.Reflection
命名空间中的类型,其中包含 Type
、Assembly
、MethodInfo
等关键类。
1)核心类与命名空间
System.Reflection
:反射的核心功能- Assembly:表示程序集,用于加载和操作程序集中的模块、类型等信息。
- MethodInfo/PropertyInfo/FieldInfo:分别对应方法、属性、字段的元数据,支持动态调用和访问。
System.Type
:代表任意类型(如类、接口、枚举等),可通过typeof
获取类型信息。
2)相关类与命名空间
基本列举反射实际应用时所涉及的所有类与命名空间
System.Reflection(命名空间)
|
├── Assembly // 程序集操作
├── Module // 模块信息
├── ConstructorInfo // 构造函数
├── ParameterInfo // 参数信息
├── MethodInfo // 方法信息
├── PropertyInfo // 属性信息
├── MemberInfo // 成员信息
├── FieldInfo // 字段信息
├── TypeInfo // 类型信息
└── MethodBase // 方法基类信息
|
System(命名空间)
|
├── Type // 类型元数据
├── Activator // 实例创建
└── AppDomain // 应用程序域管理
反射常用类与方法速查表
类/方法 | 用途 | 示例代码 |
---|---|---|
Type | 获取类型元数据 | typeof(MyClass) |
Assembly | 加载和操作程序集 | Assembly.Load("MyAssembly") |
MethodInfo | 获取和调用方法 | method.Invoke(obj, args) |
PropertyInfo | 访问属性值 | property.GetValue(obj) |
BindingFlags | 控制成员可见性(如私有) | BindingFlags.NonPublic |
Activator.CreateInstance | 动态创建对象实例 | Activator.CreateInstance(type) |
4. 简单示例
动态创建对象并调用方法
// 获取类型
Type type = typeof(MyClass);
// 动态创建实例
object instance = Activator.CreateInstance(type);
// 获取方法并调用
MethodInfo method = type.GetMethod("SayHello");
method.Invoke(instance, null); // 输出 "Hello, World!"
Type type = typeof(string); // 获取类型信息
object obj = Activator.CreateInstance(type); // 动态创建实例
二、如何使用反射
0. 反射操作流程概览
1. 获取类型信息
Type
对象是反射的基础,提供了关于类型的详细信息。
1)获取Type
对象
要使用反射,首先需要获取类型的Type
对象。在C#中,可以通过多种方式获取类型信息。
// 方式1:typeof运算符(编译时已知类型)
Type type1 = typeof(StringBuilder);// 方式2:GetType()(运行时对象实例)
object obj = new List<int>();
Type type2 = obj.GetType();// 方式3:Type.GetType()(通过类型名称)
Type type3 = Type.GetType("System.String");
关于获取Type对象的方式,详细信息可见:C# 获取Type对象的方式。
2)获取 Type 类中的基本属性
▶ 获取类型的基本信息
- Name: 类型的简单名称。
- FullName: 类型的完全限定名称(包含命名空间)。
- Namespace: 类型所在命名空间的名称。
- AssemblyQualifiedName: 获取类型的程序集限定名,包含类型及其所在程序集的详细信息,适用于通过反射加载类型。
namespace ReflectionDemo
{public class User { }internal class Program{static void Main(string[] args){var type = typeof(User);Console.WriteLine($"{"Name".PadRight(24)}:{type.Name}");Console.WriteLine($"{"FullName".PadRight(24)}:{type.FullName}");Console.WriteLine($"{"Namespace".PadRight(24)}:{type.Namespace}");Console.WriteLine($"{"AssemblyQualifiedName".PadRight(24)}:{type.AssemblyQualifiedName}");}}
}
运行结果:
Name :User
FullName :ReflectionDemo.User
Namespace :ReflectionDemo
AssemblyQualifiedName :ReflectionDemo.User, ReflectionDemo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
关于以上属性,详情内容可见:C# Type类中Name、FullName、Namespace、AssemblyQualifiedName的区别。
▶ Assembly
属性
通过Type对象的Assembly
属性 获取 类型所在的程序集。
using System.Reflection;namespace ReflectionDemo
{public class User { }internal class Program{static void Main(string[] args){var type = typeof(User);Assembly assembly = type.Assembly;Console.WriteLine($"Assembly FullName:{assembly.FullName}");Console.WriteLine($"Assembly Name:{type.Name}");}}
}
输出结果:
Assembly FullName:ReflectionDemo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Assembly Name:User
在此我们可知:
【类型的程序集限定名】= 【类型的完全限定名FullName】+【程序集的完全限定名FullName】
当我们通过Type对象的Assembly
属性 获取 类型所在的程序集之后,我们可以对程序集进行相关的操作,具体关于Assembly
的相关内容将在下文进行详细介绍。
▶ 其他基础属性
namespace ReflectionDemo
{public class User { }internal class Program{static void Main(string[] args){var type = typeof(User);Console.WriteLine("基类:" + type.BaseType); // 输出:System.ObjectConsole.WriteLine($"IsAbstract:{type.IsAbstract}"); // 是否是抽象Console.WriteLine($"IsAbstract:{type.IsInterface}"); // 是否是接口Console.WriteLine($"IsAbstract:{type.IsClass}"); // 是否是类Console.WriteLine($"IsAbstract:{type.IsEnum}"); // 是否是枚举类型Console.WriteLine($"IsAbstract:{type.IsGenericType}"); // 是否是泛型Console.WriteLine($"IsAbstract:{type.IsPublic}"); // 是否PublicConsole.WriteLine($"IsAbstract:{type.IsSealed}"); // 是否SealedConsole.WriteLine($"IsAbstract:{type.IsValueType}"); // 是否值类型}}
}
3)获取类型的成员信息
反射不仅可以获取类型信息,还可以获取类型的成员信息,如字段、属性、方法等。
class Person
{public string Name { get; set; }private int age;
}
▶ 获取字段信息
class Program
{static void Main(){Type personType = typeof(Person);FieldInfo[] fields = personType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);foreach (var field in fields){Console.WriteLine($"Field: {field.Name}, Type: {field.FieldType}");}}
}
▶ 获取属性信息
class Program
{static void Main(){Type personType = typeof(Person);PropertyInfo[] properties = personType.GetProperties();foreach (var property in properties){Console.WriteLine($"Property: {property.Name}, Type: {property.PropertyType}");}}
}
▶ 获取方法信息
class Calculator
{public int Add(int a, int b) => a + b;private int Subtract(int a, int b) => a - b;
}class Program
{static void Main(){Type calculatorType = typeof(Calculator);MethodInfo[] methods = calculatorType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);foreach (var method in methods){Console.WriteLine($"Method: {method.Name}, Return Type: {method.ReturnType}");}}
}
- 更多关于
Type
类 的详情介绍,可见:C# Type 类使用详解。 - 更多关于
BindingFlags
的详细内容,可见:C# BindingFlags 使用详解
2. 动态创建对象
反射不仅限于获取类型和成员信息,还可以用于动态创建对象。
以Person
类 为例:
class Person
{public int Age { get; set; }public string Name { get; set; }public Person(){Console.WriteLine("无参构造函数被执行!");}public Person(string name){Name = name;Console.WriteLine($"有参构造函数被执行,Name = {Name}");}public Person(string name, int age){Name = name;Age = age;Console.WriteLine($"有参构造函数被执行,Name = {Name}、Age = {Age}");}public void Show(){Console.WriteLine("Person");}
}
1)使用 Activator.CreateInstance
创建对象
▶ 无参构造
internal class Program
{static void Main(string[] args){Type personType = typeof(Person);object personInstance = Activator.CreateInstance(personType);var person = (Person)personInstance;person.Show();}
}
运行结果:
无参构造函数被执行!
Person
▶ 带参数构造
- 单参数构造函数
internal class Program
{static void Main(string[] args){Type personType = typeof(Person);object[] parameters = { "Bob" };object personInstance = Activator.CreateInstance(personType, parameters);var person = personInstance as Person;person?.Show();}
}
运行结果:
有参构造函数被执行,Name = Bob
Person
- 多参数构造函数
internal class Program
{static void Main(string[] args){Type personType = typeof(Person);object[] parameters = { "Bob", 12 };object personInstance = Activator.CreateInstance(personType, parameters);var person = (Person)personInstance;person.Show();}
}
运行结果:
有参构造函数被执行,Name = Bob、Age = 12
Person
▶ 动态创建泛型实例
Type openType = typeof(List<>);
Type closedType = openType.MakeGenericType(typeof(int));
object list = Activator.CreateInstance(closedType);
Type openType = typeof(Dictionary<,>);
Type closedType = openType.MakeGenericType(typeof(int), typeof(string));
object dict = Activator.CreateInstance(closedType);
▶ 注意事项
- 使用
Activator.CreateInstance
创建具有参数化构造函数对象实例的时候,需要保持传入的参数一致 - 如
public Person(string name)
构造函数,需要的参数是object[] parameters = { "Bob" };
- 如
public Person(string name, int age)
构造函数,需要的参数是object[] parameters = { "Bob", 12 };
2)使用 Assembly
中的CreateInstance
创建对象
▶ 无参构造
internal class Program
{static void Main(string[] args){Assembly assembly = Assembly.GetExecutingAssembly();object personInstance = assembly.CreateInstance("ReflectionDemo.Person");var person = (Person)personInstance;person.Show();}
}
运行结果:
无参构造函数被执行!
Person
▶ 带参构造
internal class Program
{static void Main(string[] args){Assembly assembly = Assembly.GetExecutingAssembly();// 传递参数调用构造函数object personInstance = assembly.CreateInstance("ReflectionDemo.Person",ignoreCase: false,bindingAttr: BindingFlags.Public | BindingFlags.Instance, // 指定公共实例构造函数binder: null,//默认null 即可args: new object[] { "Bob", 12 },culture: null,//默认null 即可activationAttributes: null//默认null 即可)!;var person = (Person)personInstance;person.Show();}
}
运行结果:
有参构造函数被执行,Name = Bob、Age = 12
Person
▶ 注意事项
args
数组的类型和顺序必须与目标构造函数的参数完全匹配。Assembly.CreateInstance
相对低效,建议优先使用Activator.CreateInstance
或工厂模式。Assembly.CreateInstance
最终调用Activator.CreateInstance
,因此两者功能相似,但 Activator 更直接。
3)使用 Invoke执行构造函数 创建对象
▶ 无参构造
internal class Program
{static void Main(string[] args){Type personType = typeof(Person);// 配置构造函数参数列表var types = Type.EmptyTypes; //new Type[0];ConstructorInfo constructorInfo = personType.GetConstructor(types);// 使用Invoke 执行构造函数,并传入对应的参数 数组object personInstance = constructorInfo.Invoke(null);//(new object[0]);var person = (Person)personInstance;person.Show();}
}
运行结果:
无参构造函数被执行!
Person
▶ 带参构造
internal class Program
{static void Main(string[] args){Type personType = typeof(Person);// 配置构造函数参数列表var types = new Type[] { typeof(string), typeof(int) };ConstructorInfo constructorInfo = personType.GetConstructor(types);// 使用Invoke 执行构造函数,并传入对应的参数 数组object personInstance = constructorInfo.Invoke(new object[] { "Bob", 12 });var person = (Person)personInstance;person.Show();}
}
运行结果:
有参构造函数被执行,Name = Bob、Age = 12
Person
▶ 注意事项
- 参数数组的类型和顺序必须与目标构造函数的参数完全匹配。
- 如
new Type[] { typeof(string), typeof(int) };
和new object[] { "Bob", 12 }
保持一致
3. 动态访问和操作成员
反射不仅限于获取类型和成员信息,还可以用于动态调用方法和访问字段或属性。
1)动态调用方法
使用Type
对象的GetMethod
方法获取MethodInfo
对象,然后调用Invoke
方法执行方法。
using System.Reflection;namespace ReflectionDemo
{class Calculator{public int Add(int a, int b) => a + b;public void Show() => Console.WriteLine("Calculator");}internal class Program{static void Main(string[] args){Type calculatorType = typeof(Calculator);object calculatorInstance = Activator.CreateInstance(calculatorType);// 获取指定名称的方法信息MethodInfo showMethod = calculatorType.GetMethod("Show");MethodInfo addMethod = calculatorType.GetMethod("Add");// 执行无参方法showMethod.Invoke(calculatorInstance, null);// 执行带参方法int result = (int)addMethod.Invoke(calculatorInstance, new object[] { 5, 3 });Console.WriteLine($"Result of Add(5, 3): {result}");}}
}
运行结果:
Calculator
Result of Add(5, 3): 8
2)访问字段和属性
▶ 访问字段
使用Type
对象的GetField
方法获取FieldInfo
对象,然后使用GetValue
和SetValue
方法访问或设置属性值。
using System;
using System.Reflection;class Person
{public string Name { get; set; }private int age;
}class Program
{static void Main(){Type personType = typeof(Person);object personInstance = Activator.CreateInstance(personType);// 获取 FieldInfo 对象:私有字段FieldInfo ageField = personType.GetField("age", BindingFlags.NonPublic | BindingFlags.Instance);// 使用 SetValue 设置字段值ageField.SetValue(personInstance, 30);// 使用 GetValue 获取字段值int ageValue = (int)ageField.GetValue(personInstance);Console.WriteLine($"Person's Age: {ageValue}");}
}
▶ 访问属性
使用Type
对象的GetProperty
方法获取PropertyInfo
对象,然后使用GetValue
和SetValue
方法访问或设置属性值。
using System;
using System.Reflection;class Person
{public string Name { get; set; }
}class Program
{static void Main(){Type personType = typeof(Person);object personInstance = Activator.CreateInstance(personType);// 获取 PropertyInfo 对象PropertyInfo nameProperty = personType.GetProperty("Name");// 使用 SetValue 设置属性值nameProperty.SetValue(personInstance, "Bob");// 使用 GetValue 获取属性值string nameValue = (string)nameProperty.GetValue(personInstance);Console.WriteLine($"Person's Name: {nameValue}");}
}
三、高阶实战技巧
1. 动态加载程序集
1)获取程序集信息
反射可解析程序集的元数据,例如:
using System.Reflection;namespace ReflectionDemo
{internal class Program{static void Main(string[] args){// 获取当前程序集Assembly currentAssembly = Assembly.GetExecutingAssembly();// 获取当前程序集完全限定名称Console.WriteLine("程序集名称:" + currentAssembly.FullName);// 输出:程序集名称:ReflectionDemo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=nullConsole.WriteLine("程序集名称:" + currentAssembly.GetName().FullName);// 输出:程序集名称:ReflectionDemo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=nullConsole.WriteLine("程序集名称:" + currentAssembly.GetName().Name);// 输出:程序集名称:ReflectionDemoConsole.WriteLine("程序集版本:" + currentAssembly.GetName().Version);// 输出:程序集版本:1.0.0.0}}
}
2)获取类型信息
从程序集中获取特定的类型(类、结构等)信息。
// 获取当前程序集
Assembly currentAssembly = Assembly.GetExecutingAssembly();// 获取程序集中的所有类型
Type[] types = currentAssembly.GetTypes();// 获取指定名称的类型
Type myType = currentAssembly.GetType("Namespace.ClassName");
3)动态加载程序集
▶ 使用Assembly.Load
加载
// 通过程序集全名加载
string assemblyName = "MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
Assembly assembly = Assembly.Load(assemblyName);// 加载后自动加载依赖项(如 MyDependency.dll)
▶ 使用Assembly.LoadFrom
加载
// 通过路径加载,并自动加载依赖项
string path = @"C:\MyAssembly.dll";
Assembly assembly = Assembly.LoadFrom(path);// 如果 MyAssembly.dll 依赖 MyDependency.dll,会自动加载
▶ 使用Assembly.LoadFile
加载
// 通过路径加载,但不加载依赖项
string path = @"C:\MyAssembly.dll";
Assembly assembly = Assembly.LoadFile(path);// 手动加载依赖项(如 MyDependency.dll)
Assembly.LoadFile(@"C:\MyDependency.dll");
程序集加载的三种方式,可以在项目中添加该程序集的引用后使用,也可在未添加该程序集的情况下使用(某些情况下),这样就极大的丰富的项目的灵活性和扩展性
- 有关程序集的强名称 或弱名称 相关信息,可见:C# Type类中Name、FullName、Namespace、AssemblyQualifiedName的区别 文中所涉及的程序集的强弱名称内容。
- 有关这三种加载程序集方式的详细信息,可见:C# 动态加载程序集的三种方式
4)实现插件化架构的关键步骤
// 加载 DLL
Assembly pluginAssembly = Assembly.LoadFrom("Plugin.dll");// 获取类型
Type pluginType = pluginAssembly.GetType("Plugin.MainClass");// 创建实例
object plugin = Activator.CreateInstance(pluginType);// 调用插件方法
MethodInfo executeMethod = pluginType.GetMethod("Execute");
executeMethod.Invoke(plugin, null);
2. 访问私有成员
通过 BindingFlags 组合实现私有成员的访问:
// 访问私有字段
FieldInfo privateField = type.GetField("_privateField", BindingFlags.NonPublic | BindingFlags.Instance);
privateField.SetValue(instance, "secret");// 调用私有方法
MethodInfo privateMethod = type.GetMethod("InternalProcess",BindingFlags.NonPublic | BindingFlags.Instance);
privateMethod.Invoke(instance, null);
3. 泛型方法调用
使用MakeGenericMethod
动态创建泛型方法实例
▶ 调用单类型参数的泛型方法
using System.Reflection;
using System.Xml.Linq;namespace ReflectionDemo
{public class GenericHelper{// 定义一个泛型方法,接受类型参数 T,并打印类型名称public static void PrintGenericType<T>(){Console.WriteLine($"泛型类型参数 T 的类型是:{typeof(T).Name}");}}internal class Program{static void Main(string[] args){// 1. 获取目标类的 Type 对象Type targetType = typeof(GenericHelper);// 2. 通过反射获取泛型方法的 MethodInfo(注意方法名和参数列表)// 这里方法 PrintGenericType<T> 没有参数,所以参数类型数组为空MethodInfo genericMethod = targetType.GetMethod("PrintGenericType");if (genericMethod == null){Console.WriteLine("未找到泛型方法!");return;}// 3. 使用 MakeGenericMethod 指定类型参数(例如 int 和 string)// 创建一个类型参数数组,这里指定 T 为 intType[] typeArgs = { typeof(int) };MethodInfo closedMethod = genericMethod.MakeGenericMethod(typeArgs);// 4. 调用闭合后的泛型方法closedMethod.Invoke(null, null); // 静态方法无需实例,参数数组为空// 再次调用,指定类型参数为 stringType[] typeArgs2 = { typeof(string) };MethodInfo closedMethod2 = genericMethod.MakeGenericMethod(typeArgs2);closedMethod2.Invoke(null, null);}}
}
▶ 调用多类型参数的泛型方法
public class GenericHelper
{public static void PrintTwoTypes<T1, T2>(){Console.WriteLine($"T1: {typeof(T1).Name}, T2: {typeof(T2).Name}");}
}// 通过反射调用:
MethodInfo genericMethod = targetType.GetMethod("PrintTwoTypes");
MethodInfo closedMethod = genericMethod.MakeGenericMethod(typeof(int), typeof(string));
closedMethod.Invoke(null, null); // 输出:T1: Int32, T2: String
4.代码示例
为了更直观地理解反射的使用,下面是一个完整的代码示例。
using System;
using System.Reflection;// 定义Person类
public class Person
{public string Name { get; set; }public int Age { get; set; }public Person(string name, int age){Name = name;Age = age;}public void Introduce(){Console.WriteLine($"My name is {Name}, and I am {Age} years old.");}public string GetGreeting(){return $"Hello, my name is {Name}.";}
}// 反射示例
public class ReflectionExample
{public static void Main(){// 获取Person类型的Type对象Type personType = typeof(Person);// 使用Activator.CreateInstance方法创建Person对象object person = Activator.CreateInstance(personType, "Alice", 30);Person alice = (Person)person;// 调用Introduce方法alice.Introduce();// 使用反射调用GetGreeting方法MethodInfo greetingMethod = personType.GetMethod("GetGreeting");string greeting = (string)greetingMethod.Invoke(alice, null);Console.WriteLine(greeting);// 使用反射访问和设置Name属性PropertyInfo nameProperty = personType.GetProperty("Name");string name = (string)nameProperty.GetValue(alice);Console.WriteLine($"Name: {name}");nameProperty.SetValue(alice, "Bob");name = (string)nameProperty.GetValue(alice);Console.WriteLine($"Updated Name: {name}");}
}
在这个示例中,我们通过反射获取了 Person
类的类型信息,动态创建了对象实例,并设置了字段和属性的值,最后调用了方法。
四、反射的实际应用场景
1. 应用场景
- 插件系统开发:动态加载DLL实现功能扩展
- ORM框架:实现对象关系映射
- 序列化工具:动态解析对象结构
- DI容器:依赖注入实现
2. 应用场景示例
- 有关反射的 应用场景示例,可见:C# 反射的实际应用场景。
五、使用须知
1. 反射的优缺点
优点
- 灵活性高:在运行时动态操作对象,适用于需要灵活处理的场景。
- 功能强大:可以访问和操作程序的元数据,实现复杂的功能。适用于序列化场景。
- 强扩展性:可以动态的加载外部的程序集,增强程序的扩展性,适用于插件系统场景。
缺点
- 性能开销:反射操作通常比直接操作慢,因为它需要额外的查找和验证。
- 安全性问题:反射可以访问私有成员,可能带来安全隐患。
- 可读性差:代码可读性较差,维护难度增加。
2. 性能优化
1)性能优化策略
虽然反射提供了极大的灵活性,但其性能开销相对较高(反射涉及动态类型解析,比直接调用慢10-100倍。)。频繁使用反射可能会影响应用程序的性能,特别是在需要高效率的场景下。为了优化性能,可以考虑以下几点:
- 缓存反射结果:如果多次调用相同的反射操作,可以将结果缓存起来,避免重复查找。
- 对重复使用的
Type
对象进行缓存 - 预获取
MethodInfo
、PropertyInfo
并缓存。
- 对重复使用的
- 使用表达式树:对于某些复杂的反射操作,可以使用表达式树来生成高效的IL代码。
- 预编译表达式:使用
System.Linq.Expressions
生成动态方法 - 使用 Delegate 或 Expression:将反射调用转为委托提高性能
- 预编译表达式:使用
- 减少反射调用次数:尽量减少不必要的反射调用,尤其是在循环中。
- BindingFlags优化:精确指定成员搜索范围
2)基准测试对比
操作类型 | 直接调用 | 反射调用 | 委托缓存 | Emit IL |
---|---|---|---|---|
简单方法调用(100万次) | 5ms | 6500ms | 15ms | 8ms |
属性访问(100万次) | 2ms | 3200ms | 10ms | 6ms |
以上数据,仅供参考。
3)优化方案实现
▶ 方案1:缓存反射结果
缓存反射结果可以显著提高应用程序的性能,尤其是在频繁使用反射获取类型信息、方法调用等场景下。下面我将给出一个简单的C#示例,展示如何缓存反射的结果。
using System;
using System.Collections.Generic;
using System.Reflection;public class ReflectionCacheExample
{private static Dictionary<string, MethodInfo> _methodInfoCache = new Dictionary<string, MethodInfo>();public void ExecuteMethod(string methodName){var methodInfo = GetMethodInfo(methodName);if (methodInfo != null){// 调用方法methodInfo.Invoke(this, null);}else{Console.WriteLine($"未找到名为 {methodName} 的方法");}}private MethodInfo GetMethodInfo(string methodName){string key = $"{this.GetType().FullName}.{methodName}";// 检查缓存中是否已有该方法的信息if (!_methodInfoCache.TryGetValue(key, out MethodInfo cachedMethod)){// 如果缓存中没有,则通过反射查找并添加到缓存MethodInfo method = this.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);if (method != null){_methodInfoCache[key] = method;return method;}return null;}return cachedMethod;}// 示例方法private void PrintHello(){Console.WriteLine("Hello, World!");}
}class Program
{static void Main(string[] args){ReflectionCacheExample example = new ReflectionCacheExample();example.ExecuteMethod("PrintHello"); // 第一次调用,会进行反射查找example.ExecuteMethod("PrintHello"); // 第二次调用,直接从缓存读取}
}
代码说明
- 在这个例子中,我们创建了一个
ReflectionCacheExample
类,它包含一个用于缓存方法信息的静态字典_methodInfoCache
。 GetMethodInfo
方法首先尝试从缓存中检索方法信息。如果找不到,则通过反射获取方法信息,并将其存储在缓存中以便将来使用。ExecuteMethod
方法演示了如何利用缓存来执行方法。第一次调用时,由于缓存为空,所以需要通过反射查找方法;第二次调用时,直接从缓存中获取方法信息,提高了效率。
缓存属性信息
private static Dictionary<Type, PropertyInfo[]> _propertyCache = new();public static PropertyInfo[] GetCachedProperties(Type type)
{if (!_propertyCache.TryGetValue(type, out var props)){props = type.GetProperties();_propertyCache[type] = props;}return props;
}
▶ 方案2:委托缓存(推荐)
下面是一个完整的C#示例,展示了如何使用表达式树创建一个属性访问器(getter),并将其应用于具体的类实例中。我们将以Person
类为例,并展示如何获取其Name
属性的值。
using System;
using System.Linq.Expressions;
using System.Reflection;public class Person
{public string Name { get; set; }public int Age { get; set; }public Person(string name, int age){Name = name;Age = age;}
}public static class PropertyGetterFactory
{public static Func<object, object> CreatePropertyGetter(PropertyInfo prop){var objParam = Expression.Parameter(typeof(object), "obj");var castExpr = Expression.Convert(objParam, prop.DeclaringType);var propAccess = Expression.Property(castExpr, prop);var castResult = Expression.Convert(propAccess, typeof(object));return Expression.Lambda<Func<object, object>>(castResult, objParam).Compile();}
}class Program
{static void Main(string[] args){// 创建一个Person对象var personObj = new Person("Alice", 30);// 获取"Name"属性的PropertyInfovar propertyInfo = typeof(Person).GetProperty("Name");if (propertyInfo == null){Console.WriteLine("未找到指定的属性");return;}// 创建属性访问器var getter = PropertyGetterFactory.CreatePropertyGetter(propertyInfo);// 使用属性访问器获取属性值string name = (string)getter(personObj);// 输出结果Console.WriteLine($"Name: {name}");}
}
代码说明
-
Person 类:
- 包含两个属性:
Name
和Age
。 - 提供了一个构造函数用于初始化这两个属性。
- 包含两个属性:
-
PropertyGetterFactory 类:
CreatePropertyGetter
方法接收一个PropertyInfo
对象作为参数。- 使用表达式树构建一个从对象到其属性值的委托(
Func<object, object>
)。 - 这个委托可以直接调用,传入目标对象,返回该对象对应属性的值。
-
Main 方法:
- 创建一个
Person
实例并初始化其属性。 - 获取
Person
类的Name
属性的PropertyInfo
。 - 调用
CreatePropertyGetter
方法生成属性访问器。 - 使用生成的属性访问器获取
personObj
的Name
属性值,并输出结果。
- 创建一个
输出结果
Name: Alice
这种方式通过表达式树动态生成属性访问器,可以显著提高反射操作的性能,特别是在需要频繁访问同一属性的情况下。
// 表达式树优化
var param = Expression.Parameter(typeof(MyClass));
var propAccess = Expression.Property(param, "Name");
var lambda = Expression.Lambda<Func<MyClass, string>>(propAccess, param);
Func<MyClass, string> compiled = lambda.Compile();
string value = compiled(instance);
▶ 方案3:Emit动态生成
动态方法和IL生成是一种高级技术,通常用于性能优化或在运行时动态生成代码的场景。使用这些技术时需要小心,确保生成的IL代码是正确的并且符合预期的行为。
下面是一个完整的C#示例,展示了如何使用动态方法(DynamicMethod
)和IL生成器(ILGenerator
)来创建一个无参构造函数的委托(ObjectActivator
)。我们将以一个简单的类Person
为例,并展示如何使用这个委托实例化对象。
using System;
using System.Reflection;
using System.Reflection.Emit;public class Person
{public string Name { get; set; }public int Age { get; set; }public Person(){Name = "Unknown";Age = 0;}public override string ToString(){return $"Name: {Name}, Age: {Age}";}
}public delegate object ObjectActivator();public static class ObjectActivatorFactory
{public static ObjectActivator CreateParameterlessConstructor(Type type){// 确保类型有一个无参构造函数ConstructorInfo constructor = type.GetConstructor(Type.EmptyTypes);if (constructor == null){throw new ArgumentException("类型必须包含一个无参构造函数。", nameof(type));}// 创建一个新的动态方法var dynamicMethod = new DynamicMethod(name: "CreateInstance",returnType: typeof(object),parameterTypes: null,owner: type);ILGenerator il = dynamicMethod.GetILGenerator();il.Emit(OpCodes.Newobj, constructor); // 调用无参构造函数il.Emit(OpCodes.Ret); // 返回新创建的对象return (ObjectActivator)dynamicMethod.CreateDelegate(typeof(ObjectActivator));}
}class Program
{static void Main(string[] args){try{// 创建一个用于实例化Person对象的委托ObjectActivator activator = ObjectActivatorFactory.CreateParameterlessConstructor(typeof(Person));// 使用委托创建Person对象object personObj = activator();// 输出结果Console.WriteLine(personObj.ToString());}catch (Exception ex){Console.WriteLine($"发生错误: {ex.Message}");}}
}
代码说明
-
Person 类:
- 包含两个属性:
Name
和Age
。 - 提供了一个无参构造函数,初始化
Name
为"Unknown",Age
为0。 - 重写了
ToString
方法,方便输出对象信息。
- 包含两个属性:
-
ObjectActivatorFactory 类:
CreateParameterlessConstructor
方法接收一个Type
对象作为参数。- 首先检查该类型是否包含无参构造函数,如果没有,则抛出异常。
- 使用
DynamicMethod
创建一个新的动态方法,返回类型为object
,没有参数。 - 使用
ILGenerator
生成中间语言(IL)代码,调用指定类型的无参构造函数并返回新创建的对象。 - 最后,将动态方法编译为委托并返回。
-
Main 方法:
- 尝试创建一个用于实例化
Person
对象的委托。 - 使用该委托创建一个
Person
对象。 - 输出创建的对象信息。
- 尝试创建一个用于实例化
输出结果
Name: Unknown, Age: 0
using System;
using System.Linq.Expressions;
using System.Reflection.Emit;public class Program
{public static void Main(){// 动态生成一个方法DynamicMethod dynamicMethod = new DynamicMethod("Add", typeof(int), new[] { typeof(int), typeof(int) }, typeof(Program).Module);ILGenerator il = dynamicMethod.GetILGenerator();il.Emit(OpCodes.Ldarg_0);il.Emit(OpCodes.Ldarg_1);il.Emit(OpCodes.Add);il.Emit(OpCodes.Ret);// 调用动态生成的方法Func<int, int, int> add = (Func<int, int, int>)dynamicMethod.CreateDelegate(typeof(Func<int, int, int>));Console.WriteLine(add(2, 3)); // 输出: 5}
}
4)反射替代方案
场景 | 反射方案 | 替代方案 |
---|---|---|
高性能方法调用 | MethodInfo.Invoke | 表达式树编译委托 |
对象创建 | Activator.CreateInstance | 预编译工厂类 |
类型检查 | IsAssignableFrom | 泛型约束/模式匹配 |
元数据分析 | GetCustomAttributes | 源代码生成 |
3. 安全注意事项
- 访问私有成员破坏封装性,可能导致代码脆弱。
- 反射可绕过权限检查,需谨慎处理敏感操作。
- 避免反射未信任的程序集,防止安全漏洞。
4. 反射开发原则
- 最小化原则:只在必要时使用反射(仅在动态扩展、框架开发等必要场景使用反射。 )
- 缓存原则:避免重复反射操作
- 安全原则:严格校验输入参数
- 性能原则:优先使用编译时方案
- 封装原则:封装反射逻辑,将反射操作封装在工具类中,降低业务代码复杂度。
六、反射与dynamic
关键字
1. 替代方案
- 对于简单场景,优先使用
dynamic
关键字:dynamic obj = new Person(); obj.Name = "李四"; // 动态绑定
2. 反射与 dynamic
的区别
- 反射:通过
Type
对象显式操作类型成员,灵活性高但性能低。 - dynamic:编译时静态类型,运行时动态绑定,语法简洁但功能受限。
3. 反射与dynamic
internal class Program
{static void Main(string[] args){Type type = typeof(User);object o_user = Activator.CreateInstance(type);//o_user.Show() //不可能通过o_class1 调用Showdynamic d_user = Activator.CreateInstance(type);d_user.Show("sss");//可以通过d_user 调用方法Show//其实o_user 和 d_user得到结果都是一样的,// 但是因为 object 时编译时类型,object本身没有Show方法,因此调用会报错// 而dynamic 是运行时类型,编译状态下会绕过编译器的检查,直到真正运行后才确定其数据类型Console.ReadLine();}
}
结语
回到目录页:C#/.NET 知识汇总
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。
相关文章:
深入理解 C# 反射 的使用
总目录 前言 反射是.NET框架中一个强大的特性,允许程序在运行时检查和操作类型信息。通过反射,开发者可以动态地创建对象、调用方法、访问属性等,为程序提供了极大的灵活性。本文将详细讲解C#反射的使用方法及其应用场景。 一、什么是反射&a…...
Java面试第十三山!《设计模式》
大家好,我是陈一。如果文章对你有帮助,请留下一个宝贵的三连哦~ 万分感谢! 一、设计模式入门指南 1. 什么是设计模式? 设计模式是可复用的解决方案模板,用于解决软件开发中常见的架构问题。如同建筑领域的…...
AI+视频赋能智慧农业:EasyCVR打造全域可视化农场监管平台
随着科技的飞速发展,传统农业正加速向智慧农业转型,农场管理也迎来了前所未有的变革机遇。在这一进程中,如何有效整合先进的信息技术,实现农场的精准化、智能化管理,成为了摆在农场主和农业管理者面前的关键课题。 基于…...
wsl2配置xv6全解(包括22.04Jammy)
文章目录 获取xv6源代码Ubuntu20.04 Version安装指令成功测试参考MIT2021年官方文档 24.04 Version安装指令成功测试参考MIT2024年官方文档 Ubuntu 22.04没有官方文档? 配置大体流程1. 卸载原本qemu(如果之前安装了)2. clone qemu官方源代码&…...
区块链技术的应用场景和优势
区块链技术是一种分布式数据库技术,它的应用场景和优势包括但不限于以下几点: 金融领域:区块链可以用于数字货币的交易和结算,实现去中心化的金融交易,提供更安全、透明和高效的支付方式;另外,也…...
基于深度学习的相位调制算法步骤
1.构建网络结构 2.制作数据集 3.训练网络 4.引入评价指标 5.迭代优化 总结 通过以上步骤,可以实现基于深度学习的相位调制算法: 使用 U-Net 构建神经网络。 生成数据集并训练网络。 使用训练好的网络预测相位分布。 通过相关系数 γ 评估调制效果&…...
Linux的I2C总线的原理和结构详解
Linux的I2C总线的原理和结构讲解 我前面基本已经吃透了Platform总线,关于Platform总线的原理和结构,详情见下面三篇博文: https://blog.csdn.net/wenhao_ir/article/details/145023181 https://blog.csdn.net/wenhao_ir/article/details/14…...
深入理解Linux中的SCP命令:使用与原理
在Linux系统中,文件传输是一个常见的操作。无论是将文件从本地传输到远程服务器,还是从远程服务器下载文件到本地,SCP(Secure Copy Protocol)都是一个非常实用的工具。本文将详细介绍SCP命令的使用方法,并深…...
【Android】VehiclePropertyAccess引起CarService崩溃
VehiclePropertyAccess引起CarService崩溃 VehiclePropertyAccess VehiclePropertyAccess属性,用于定义车辆属性的访问权限。权限包括 读:READ,只可以读取,不能写入。 VehiclePropertyAccess:READ写:WRITE…...
小米AX6000解锁ssh避坑笔记
经过网上教程不断尝试,终于解锁成功。 环境信息: Win10 笔记本 + AX210 WIFI6E网卡Vmware 16小米AX60000.可以先备份路由器的配置信息 1.首先降级小米AX6000到1.0.55 1.0.55下载路径 升级时注意: 清除当前所有用户配置升级完成后,选择不自动升级2.升级完成后,笔记本重新…...
论华为 Pura X 折叠屏性能检测
在科技浪潮中,折叠屏手机以其创新形态掀起市场热潮。华为 Pura X 作为华为最新折叠手机,承载前沿科技与精湛工艺,成为行业焦点。它融合先进折叠屏技术与优质材质,致力于打破传统手机使用边界,为用户开启全新体验。但产…...
关于极端场景下,数据库更新与 MQ 消息一致性保障方案的详细总结
目录 一、核心问题场景 二、RocketMQ 事务消息方案 1. 核心机制 2. 执行流程 3. 关键优势 4. 局限性 三、消息表方案 1. 核心机制 2. 执行流程 3. 关键优势 4. 局限性 四、方案对比与选择 五、实施建议 六、总结 一、核心问题场景 当数据库更新后,若 MQ 消息未…...
面试题精选《剑指Offer》:JVM类加载机制与Spring设计哲学深度剖析-大厂必考
一、JVM类加载核心机制 🔥 问题5:类从编译到执行的全链路过程 完整生命周期流程图 关键技术拆解 编译阶段 查看字节码指令:javap -v Robot.class 常量池结构解析(CONSTANT_Class_info等) 类加载阶段 // 手动加载…...
透析主流CSS预处理器的区别
Sass 和 Less 是两种主流的 CSS 预处理器(CSS Preprocessor),它们通过扩展原生 CSS 的语法,提供了变量、嵌套、混合(Mixins)、函数等高级功能,帮助开发者编写更高效、可维护的样式代码。以下是它…...
Redis 本地安装
首先安装: https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-from-source/ 进入root目录 tar -xzvf redis-stable.tar.gz cd redis-stable make然后 install sudo make install最后可以直接启动 redis-server但是此时启…...
Android Launcher3 首屏图标锁定技术方案解析
一、需求背景与技术挑战 在Android 13系统定制开发中,需实现Launcher首屏图标固定功能。该需求需在以下技术维度进行突破: 拖拽事件拦截机制:需精准识别拖拽目标区域 布局层级判定:准确识别第一屏的布局标识 跨屏操作限制&…...
MySQL 处理重复数据:保留一条与两条的实现方案
在数据库管理中,处理重复数据是一项常见的任务。本文将详细介绍如何在 MySQL 数据库里,针对 test 表中 fd 和 fe 字段存在的重复数据进行处理,分别实现保留一条和两条数据的操作。 表结构与需求概述 假设 test 表包含三个字段:id…...
Go红队开发—CLI框架(一)
CLI开发框架 命令行工具开发,主要是介绍开发用到的包,集成了一个框架,只要学会了基本每个人都能开发安全工具了。 该文章先学flags包,是比较经典的一个包,相比后面要学习的集成框架这个比较自由比较细化点࿰…...
deque
deque概念 双端数组,可以对头端进行插入删除操作 deque和vector差别(就像数据结构中的栈和队列) vector对于头部的插入删除效率低,而deque则相对高效 vector和deque都支持随机访问,但是vector的随机访问效率低,而deque则相对高效…...
【Oracle资源损坏类故障】:详细了解坏块
目录 1、物理坏块与逻辑坏块 1.1、物理坏块 1.2、逻辑坏块 2、两个坏块相关的参数 2.1、db_block_checksum 2.2、db_block_checking 3、检测坏块 3.1、告警日志 3.2、RMAN 3.3、ANALYZE 3.4、数据字典 3.5、DBVERIFY 4、修复坏块 4.1、RMAN修复 4.2、DBMS_REPA…...
数据分析处理库-Pandas
1.1 Pandas概述 核心概念: Pandas 是基于 NumPy 的数据分析库,核心数据结构:Series(一维)和 DataFrame(二维)。 应用场景:数据清洗、转换、统计分析、时间序列处理。 特点&#x…...
阿里云平台Vue项目打包发布
目录: 1、vue项目打包2、通过ngixn发布vue的打包文件 1、vue项目打包 在你的vue项目下执行npm run build命令进行打包。 2、通过ngixn发布vue的打包文件 直接将打包的dist文件拷贝到nginx目录下即可。 修改nginx.conf的配置文件的相关配置,如端口或者ro…...
2025/03/19 Cursor使用方法(Java方向,适合Java后端把家从idea搬家到cursor)
Cursor介绍 官网:Cursor - The AI Code Editor 中文教程网:学习 Cursor ,拥抱 AI 编程 | Cursor 101 Cursor 是一款专为程序员打造的集成开发环境(IDE),它结合了大语言模型的能力,旨在提高开发效率. 与传统的 IDE&…...
平台与架构:深度解析与开发实践
平台与架构:深度解析与开发实践 1. 什么是平台与架构? 平台(Platform):指操作系统或运行环境,例如 linux、windows、darwin(macOS)、android 等。架构(Architecture&…...
xss-labs第八、九关卡以及XSS GAME的Ok,Boomer关卡
第八关 靶场代码 <!DOCTYPE html><!--STATUS OK--><html> <head> <meta http-equiv"content-type" content"text/html;charsetutf-8"> <script> window.alert function() { confirm("完成的不错&#…...
electron框架(1.0)认识electron和基础创建
----什么是electron框架 中文网地址(https://electronjs.p2hp.com/docs/latest/tutorial/quick-start) ----electron流程模型 ----项目搭建 --起步(需下载): node -v npm -v--创建初始文件: mkdir my-e…...
考OCP认证要交哪些费用?
考OCP认证要交哪些费用? 考OCP认证,指的是Oracle数据库管理员中级认证 Oracle Certified Professional,这是Oracle非常有名的一个认证,对于个人帮助巨大。 OCP认证要交不少钱,些费用因考试版本、培训机构和地区差异而有所不同&a…...
基于漂浮式海上风电场系统的浮式风力发电机matlab仿真
目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于漂浮式海上风电场系统的浮式风力发电机matlab仿真,通过MATLAB数值仿真对浮式风力发电机的性能做模拟与仿真。 2.系统仿真结果 3.核心程序与模型 版本&#x…...
Jupyter Notebook 常用命令(自用)
最近有点忘记了一些常见命令,这里就记录一下,懒得找了。 文章目录 一、文件操作命令1. %cd 工作目录2. %pwd 显示路径3. !ls 列出文件4. !cp 复制文件5. !mv 移动或重命名6. !rm 删除 二、代码调试1. %time 时间2. %timeit 平均时长3. %debug 调试4. %ru…...
RabbitMQ 详细原理解析
RabbitMQ 是一个基于 AMQP(Advanced Message Queuing Protocol) 协议的开源消息代理中间件,广泛用于分布式系统中的异步通信、服务解耦、流量削峰等场景。其核心设计围绕生产者、消费者、队列、交换机和虚拟主机等组件,结合 AMQP …...
HTTP状态码全解析
1. 状态码分类 类别范围含义1xx100-199信息性:请求被接收,需进一步处理(临时响应)2xx200-299成功:请求被正确处理3xx300-399重定向:需后续操作完成请求(如跳转到新URL)4xx400-499客…...
从零实现本地文生图部署(Stable Diffusion)
1. 依赖安装 文件打包下载地址(Stable Diffusion) # git : 用于下载源码 https://git-scm.com/downloads/win # Python 作为基础编译环境 https://www.python.org/downloads/ # Nvidia 驱动,用于编译使用GPU显卡硬件 https://ww…...
手撕算法——链表
算法基础——链表-CSDN博客 一、排队顺序 题⽬来源:洛⾕ 题⽬链接:B3630 排队顺序 - 洛谷 难度系数:★ 1. 题目描述 2. 算法原理 本题相当于告诉了我们每⼀个点的后继,使⽤静态链表的存储⽅式能够很好的还原这个队列。 数组中 [1,…...
css-grid布局
文章目录 1、布局2、网格轨道3、间距Gap4、网格线5、网格别名 当一个 HTML 元素将 display 属性设置为 grid 或 inline-grid 后,它就变成了一个网格容器,这个元素的所有直系子元素将成为网格元素。 1、布局 启用grid布局类似与flex布局,不过g…...
1.企业级AD活动目录核心解析:架构、组件与集成实践
在当今数字化时代,企业级网络环境日益复杂,高效、安全的资源管理和用户认证成为企业 IT 运营的关键。AD(Active Directory)活动目录作为微软 Windows 系列服务器中的重要目录服务,为企业级网络管理提供了强大的解决方案…...
哈尔滨工业大学DeepSeek公开课人工智能:大模型原理 技术与应用-从GPT到DeepSeek|附视频下载方法
导 读INTRODUCTION 今天继续哈尔滨工业大学车万翔教授带来了一场主题为“DeepSeek 技术前沿与应用”的报告。 本报告深入探讨了大语言模型在自然语言处理(NLP)领域的核心地位及其发展历程,从基础概念出发,延伸至语言模型在机器翻…...
ChatGPT vs DeepSeek vs Copilot vs Claude:谁将问鼎AI王座?
李升伟 整理 2025年的人工智能领域创新涌动,ChatGPT、DeepSeek、Copilot和Claude四大模型各领风骚。这些AI系统各具特色,分别专注于编程、创意写作、技术推理和AI伦理等不同领域。本文将深入解析这些AI模型的功能特性及其优势领域。 核心AI模型解析 C…...
【嵌入式Linux】基于ArmLinux的智能垃圾分类系统项目
目录 1. 功能需求2. Python基础2.1 特点2.2 Python基础知识2.3 dict嵌套简单说明 3. C语言调用Python3.1 搭建编译环境3.2 直接调用python语句3.3 调用无参python函数3.4 调用有参python函数 4. 阿里云垃圾识别方案4.1 接入阿里云4.2 C语言调用阿里云Python接口 5. 香橙派使用摄…...
Vue3中router最佳封装落地
文章目录 前言一、拆分路由文件夹?二、main.ts中注册路由总结 前言 router在使用过程中如果我们直接在一个文件的一个数组中配置,最后路由越来越多会导致不易管理,我们可以将一个页面的路由配置在一个数组中最后统一导入,这样就会…...
[Linux] make自动化构建
目录 一.什么是make 二.Makefile结构 2.1 典型结构 2.2 变量 1. 普通变量(User-Defined Variables) 2. 自动变量(Automatic Variables) 3. 预定义变量(Built-in Variables) 4. 函数变量࿰…...
剑指 Offer II 113. 课程顺序
comments: true edit_url: https://github.com/doocs/leetcode/edit/main/lcof2/%E5%89%91%E6%8C%87%20Offer%20II%20113.%20%E8%AF%BE%E7%A8%8B%E9%A1%BA%E5%BA%8F/README.md 剑指 Offer II 113. 课程顺序 题目描述 现在总共有 numCourses 门课需要选,记为 0 到 n…...
蓝桥杯 小球反弹
问题描述 有一个长方形,长为 343720 单位长度,宽为 233333 单位长度。 在其内部左上角顶点有一小球(无视其体积),其初速度方向如图所示,且保持运动速率不变。分解到长宽两个方向上的速率之比为࿱…...
Python 监听模式(Observer Pattern)
1. 监听模式技术方案 监听模式(Observer Pattern)是一种行为设计模式,允许对象(称为“观察者”或“监听者”)在另一个对象(称为“被观察者”或“主题”)的状态发生变化时接收通知。这种模式的核…...
蓝桥备赛(25)算法篇【差分】
一、差分 前缀和和差分的核心思想是预处理 , 可以在暴力枚举的过程中 , 快速给出查询结果 , 从而优化时间复杂度 。 最经典的用空间替换时间的做法。 学完差分之后 , 大家会发现 , 前缀和与差分是一对互逆的运算 二、一…...
Linux|fork命令及其使用的写时拷贝技术
fork复制进程 fork通过以下步骤来复制进程: 分配新的进程控制块:内核为新进程分配一个新的进程控制块(PCB),用于存储进程的相关信息,如进程 ID、状态、寄存器值、内存指针等。复制进程地址空间࿱…...
sgpt 终端使用指南
1. 什么是 sgpt? sgpt 是一个基于 OpenAI API 的命令行工具,允许用户在终端中与 AI 进行交互,支持自然语言对话、代码生成、Shell 命令生成等功能。本文将介绍 sgpt 的安装方法、基本用法、配置文件路径及修改方式,并提供完整的配…...
python如何提取html中所有的图片链接
在Python中,你可以使用BeautifulSoup库来解析HTML内容,并提取其中所有的图片链接(即<img>标签的src属性)。以下是一个示例代码,展示了如何做到这一点: 首先,确保你已经安装了BeautifulSo…...
第十一章 | 智能合约主网部署与验证详解
📚 第十一章 | 智能合约主网部署与验证详解 ——让你的合约真正上线、公开、透明! ✅ 本章导读 前面我们写了各种合约,ERC20、NFT、DAO…… 但只在本地测试或测试网上部署运行,项目还没“上链”! 主网上线部署&#…...
一文读懂Python之json模块(33)
一、json模块介绍 json模块的功能是将序列化的json数据从文件里读取出来或者存入文件。json是一种轻量级的数据交换格式,在大部分语言中,它被理解为数组(array)。 json模块序列化与反序列化的过程分别是 encoding和 decoding。e…...
TextView、AppCompatTextView和MaterialTextView该用哪一个?Android UI 组件发展史与演进对照表
在 Android 开发中,UI 组件一直在不断演进,从最初的原生组件,到 Support Library(AppCompat 兼容库),再到如今的 Material Design 组件。这篇文章将梳理 Android UI 组件的发展历史,并提供详细的…...