C#中 借助委托|反射|表达式来传递类对象属性的“引用”

一、知识来源

C#中如何借助委托|反射|表达式来传递类对象属性的“引用”

二、函数中,属性和字段的区别

1、字段(Field)是类或结构体中的成员变量。它们通常用于存储对象的状态和数据。字段可以是公共的(public)、私有的(private)或受保护的(protected),具体取决于其访问修饰符。字段可以直接在类或结构体中使用,并且可以直接访问和修改它们的值。

2、属性(Property)是一种特殊的类成员,它提供了对私有字段的访问和操作的公共接口。属性通常用于封装私有字段,以便在外部代码中访问和修改它们。属性有一个或多个访问器(getter和setter),通过这些访问器可以控制对属性的读取和写入操作。访问器可以添加额外的逻辑,例如验证输入值的有效性、触发事件等。属性也可以具有不同的访问修饰符,以控制对属性的可见性。

3、区别

  • 属性是用于访问类的成员的特殊成员。实际上是方法,只是在语法上看起来像字段一样。
  • 使用字段直接访问和修改数据,而属性提供了更高层次的封装和访问控制(get,set方法)。
  • 字段通常用于存储对象的状态和数据,而属性则用于对外部代码提供统一的访问接口。
  • 属性可以在其访问器中添加额外的逻辑,例如验证和处理输入值,触发事件等。
  • 对字段的访问没有副作用,而对属性的访问可能会触发属性访问器中定义的逻辑(自定义的get、set方法)。
  • 在使用时,如果你只需要简单地存储和访问数据,可以使用字段。如果你需要对数据进行封装、验证或添加其他逻辑,则应该使用属性。属性可以提供更灵活和可控的方式来访问和修改数据。

三、闭包

1、闭包是指可以访问其自身范围之外的变量的函数。这意味着一个闭包可以访问定义在其外部作用域的变量。

  • 在C#中,闭包通常是通过使用匿名方法或Lambda表达式来实现的
    2、优点
  • 可以访问外部作用域的变量,使得代码更加灵活和功能强大。
  • 可以捕获局部变量的状态,使得函数可以记住其创建时的上下文信息。
  • 可以用于创建延迟执行的代码块,方便异步编程和事件处理。
    3、缺点
  • 可能导致内存泄漏,因为闭包会持有对外部作用域中变量的引用,如果闭包的生命周期比外部作用域中的变量更长,就可能导致内存泄漏。
  • 可能导致代码可读性和维护性下降,因为闭包可以访问外部作用域的变量,导致代码的数据流向变得不清晰。

四、通过泛型方法修改字段值

1、代码

var demo = new Demo();
demo.ProValue = 100;
demo.FielValue = 100;


// 修改字段值
ModifyFieldValue(ref demo.ProValue, 199); // 报错,属性或索引器不能作为 ref 或 out参数传递
ModifyFieldValue(ref demo.FielValue, 199);
void ModifyFieldValue<T>(ref T field, T newValue)
{
    field = newValue;
}
Console.WriteLine(demo.FielValue);

class Demo
{
    public int ProValue { get; set; } // 属性
    public int FielValue; // 字段
}

2、在C#中,为什么属性和索引器不能作为ref或out的参数

  • 属性和索引器是用于访问类的成员的特殊成员。实际上是方法,只是在语法上看起来像字段一样。对属性赋值底层是调用了set方法。
  • 属性和索引器并不是存储数据的位置,而是一种用于访问和操作数据的方法。它们通常会返回一个值,而不是修改传入的参数。
  • 属性和索引器的使用方式是通过类的实例或者类本身来访问的,而不是直接传递给方法作为参数。

五、通过委托参数修改属性值

1、改造二中的代码,使得能够通过通用函数修改属性值
2、代码

var demo = new Demo();
demo.ProValue = 100;
demo.FielValue = 100;

// 修改字段、属性值
ModifyPropValueWithDelegate(val => demo.ProValue = val, 42);
ModifyPropValueWithDelegate(val => demo.FielValue = val, 88);
Console.WriteLine(demo.ProValue);
Console.WriteLine(demo.FielValue);

void ModifyPropValueWithDelegate<T>(Action<T> func,T newValue)
{
    func.Invoke(newValue);
}

class Demo
{
    public int ProValue { get; set; } // 属性
    public int FielValue; // 字段
}

3、解析

  • 这里使用了闭包。闭包有内存泄漏风险。
  • 使用起来局限性也比较大。

六、用反射 修改字段、属性值

1、代码

using System.Reflection;

var demo = new Demo();
demo.ProValue = 100;
demo.FielValue = 100;


// 反射 修改字段、属性值
var method = typeof(Demo).GetProperty("ProValue")!.GetSetMethod();
ModifyPropValueWithReflection(method, demo, 42);
void ModifyPropValueWithReflection<TClass,TProp>(MethodInfo method,TClass target, TProp newValue)
{
    method.Invoke(target, new object?[] { newValue });
}
Console.WriteLine(demo.ProValue);

class Demo
{
    public int ProValue { get; set; } // 属性
    public int FielValue; // 字段
}

2、解析

  • 通过typeof(Demo)获取到Demo类的Type对象


  • 然后调用GetProperty("ProValue")方法获取到名为"ProValue"的属性(ProValue)对应的PropertyInfo对象。


  • 使用GetSetMethod()方法获取到该属性的Set方法对应的MethodInfo对象。


  • ModifyPropValueWithReflection方法,method表示要修改的属性的Set方法的MethodInfo对象,target表示要修改的对象,newValue表示要设置的新值。

  • 这段代码利用反射机制获取到Demo类中名为"ProValue"的属性的Set方法,然后通过反射调用该方法来修改对象的属性值。

七、表达式 修改字段、属性值

1、通过反射的方式编码起来比较费劲
2、代码

using System.Linq.Expressions;
using System.Reflection;

var demo = new Demo();
demo.ProValue = 100;
demo.FielValue = 100;


// 表达树 修改字段、属性值
ModifyPropValueWithExpression(d=>d.ProValue,42, demo);
void ModifyPropValueWithExpression<TClass,TProp>(Expression<Func<TClass, TProp>> expression, TProp newValue, TClass target)
{
    var body = (MemberExpression)expression.Body;
    var prop = (PropertyInfo)body.Member;
    var setMethod = prop.GetSetMethod();
    setMethod.Invoke(target, new object?[] { newValue });
}
Console.WriteLine(demo.ProValue);

class Demo
{
    public int ProValue { get; set; } // 属性
    public int FielValue; // 字段
}

linq pad解析的树结构图
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容