• c#中字段和属性的区别,委托和事件的区别


    IDE眼里的字段和属性

    class Test
    {
    	public int age1 = 12;
    	public int Age2 { get; set; } = 18;
    
    	public void Show()
    	{
    		Console.WriteLine(age1++);
    		Console.WriteLine(Age2++);
    	} 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    很多新人发现在类中定义变量时,有些人会在后面写上get,set
    这种写法定义出来的变量,在使用的时候看起来和普通的变量没有区别。
    所以不理解这样做有什么意义。

    首先,我们去掉Age2。只关注age1.
    用你的IDE对他点右键,快速重构中会出现两个选项,封装字段并使用字段/并使用属性。
    在这里插入图片描述

    class Test
    {
    	private int age1 = 12;
    
    	public int Age1 { get => age1; set => age1 = value; }
    
    	public void Show()
    	{
    		Console.WriteLine(age1++);//如果选择使用属性,那么此句会使用Age1
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    无论选择哪一种,类中都会出现一个Age1,并且他的内容是相同的。
    然后,根据你选择的使用属性 / 仍使用字段,下面对这个变量的调用会替换为age1 / Age1
    得出结论,没有get,set的东西是字段,带有get,set的东西叫做属性。

    然后,对Age1点右键,快速重构为自动属性.
    在这里插入图片描述

    class Test
    {
    	public int Age1 { get; set; } = 12;
    
    	public void Show()
    	{
    		Console.WriteLine(Age1++);
    		//如果选择使用属性,那么此句会使用Age1
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这说明,{ get; set; }写法的叫自动属性,是一种简略的写法。
    他的完整写法应该包含一个字段,并在get,set中添加逻辑内容去控制一个字段。

    匿名字段

    在使用自动属性时,编译器会自动创建一个你无法访问的字段,这称为匿名字段。
    由于无法访问到匿名字段,所以无法为他决定初始值。因此,自动属性改为直接在属性上赋值初始值。
    public int Age1 { get; set; } = 12;
    但是,对于完整属性,public int Age1 { get => age1; set => age1 = value; }是不能这样写的。

    事件

    事件和属性类似,同样是一种缩略写法,同样引入了匿名字段。

    class Test
    {
    	public Action action = () => { };
    	public event Action Action = () => { };
    
    	public void Show()
    	{
    		action();
    		Action();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    事件和委托的使用方式一样,所以看不出来区别。但如果将事件写全,那么事件就不能赋值或调用。

    class Test
    {
    	public Action action = () => { };
    	public event Action Action { add => action += value; remove => action -= value; } //= () => { };
    
    	public void Show()
    	{
    		action(); 
    		//Action();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    继承中的字段和属性

    程序里的东西可以分类为储存的值,或可以执行的指令。

    在类中,属性和事件是可以设置为虚拟,抽象,重写的。这说明属性和事件更与方法类似。
    在接口中,不能存在字段,但是可以存在属性和事件。这说明属性和事件有别于字段。

    interface ITest
    {
    	public event Action Action;
    	public int Age { get; set; }
    }
    abstract class BTest : ITest
    {
    	public abstract int Age { get; set; }
    
    	public abstract event Action Action;
    }
    class DTest : BTest
    {
    	public override int Age { get { return default; } set { } }
    
    	public override event Action Action { add { } remove { } }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    并且,在这个抽象类中,不能为属性或事件赋值初始值,也不能像使用委托一样调用这个事件。
    这再次说明了,属性和事件不储存值。我们平常对他们的调用都是转为调用一个匿名字段的。

    反射眼里的字段和属性

    声明两个类,其中一个属性和事件写上空的逻辑。

    class Test1
    {
    	public int Age { get; set; }
    	public event Action Action;
    }
    class Test2
    {
    	public int Age { get => default; set { } }
    	public event Action Action { add { } remove { } }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    使用反射查看这两个类里面所有的东西。

    Type t1=typeof(Test1);
    Type t2=typeof(Test2);
    
    foreach (var item in t1.GetMembers((BindingFlags)(-1)))
    {
        Console.WriteLine(item.Name);
    }
    Console.WriteLine("=========");
    foreach (var item in t2.GetMembers((BindingFlags)(-1)))
    {
    	Console.WriteLine(item.Name);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    get_Age
    set_Age
    add_Action
    remove_Action
    .ctor
    Age
    Action
    k__BackingField
    Action
    =========
    get_Age
    set_Age
    add_Action
    remove_Action
    .ctor
    Age
    Action
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里面Age是属性,get_Age和set_Age分别是属性里面的get访问器和set访问器。
    同样的,Action,add_Action,remove_Action这三个东西都是事件生成的东西。
    然后.ctor是构造器 / 构造方法 / 构造函数。

    最后,上面的类型多出来了一个k__BackingField和一个Action。
    这两个东西就是匿名字段。验证代码如下。

    Type t1 = typeof(Test1);
    FieldInfo field1 = t1.GetField("k__BackingField", (BindingFlags)(-1));
    
    Test1 test = new Test1();
    Console.WriteLine(test.Age);
    
    field1.SetValue(test, 666);
    Console.WriteLine(test.Age);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    最后,在类型上。反射出来的属性和事件,可以获取他们的访问器。此方法在名字上,和返回值的类型上,
    都认为访问器是一种方法。也就是说属性和事件是包含方法的东西。

    Type type = null;//此代码仅展示类型,运行会有异常。
    MethodInfo method = type.GetMethod("");//获取方法
    
    PropertyInfo property1 = type.GetProperty("");//获取属性
    MethodInfo get = property1.GetGetMethod();//获取get访问器,以方法形式
    MethodInfo set = property1.GetSetMethod();//获取set访问器,以方法形式
    
    EventInfo eventInfo = type.GetEvent("");
    MethodInfo add = eventInfo.GetAddMethod();
    MethodInfo remove = eventInfo.GetRemoveMethod();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    并且在属性实例和事件实例这两个类型里,也确实没有任何能获取包含的值的方法。
    所以,属性和事件的本质工作并不包含储存一个值。

  • 相关阅读:
    python绘图文字乱码
    对进程、线程和协程的理解以及它们的区别
    Git 的基本概念和使用方式
    背包问题的贪心算法
    Three.JS游戏开发入门
    vue2升级vue3的新变化
    Simulink之输出模块Goto和输入模块From
    C++.I/O流
    【UE 材质】制作加载图案(2)
    openvino_datawhale
  • 原文地址:https://blog.csdn.net/zms9110750/article/details/132793193