C#学习笔记--泛型函数的==和Equals(看完你一定能学到!)

Posted 就一枚小白

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C#学习笔记--泛型函数的==和Equals(看完你一定能学到!)相关的知识,希望对你有一定的参考价值。

前言

工作的同事发现了这个问题,觉得实际游戏开发中会有这样的问题,所以在此记录

准备

  1. 开一个Unity项目,新建一个Test.cs脚本,并且生成一个Cube,直接把Test.cs挂在Cube上
  2. 写一个Nulltest.cs脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Nulltest : MonoBehaviour

    public Test test;
    private void Awake()
    
        Destroy(test);
    
    private void Update()
    
        Check(test);
    
    private void Check<T>(T test)
    
        if (test == null)
        
            print($"test == null : test == null");
            return;
        
        if (test.Equals(null))
        
            print($"test.Equals(null) : test.Equals(null)");
            return;
        
        if (ReferenceEquals(test, null))
        
            print($"ReferenceEquals(test, null) : ReferenceEquals(test, null)");
            return;
        
    


  1. 把Nulltest挂在MainCamera上,并且把Cube拖拽给test属性

验证

现在打断点验证,会发现一个很神奇的结果

我们看到vs下面的监视窗口,发现 test == null 是true,按道理应该直接return掉了,为什么还能执行到下面的语句???

查找问题

在上图中,我们看到obj是能展开的(而且值里有双引号),所以肯定是一个对象,不是null,这样应该是Component里做了或Equals的重载,于是查了一下源码,果不其然,Component继承UnityEngine.Object,而UnityEngine.Object对和Equal都进行了重载:

注意绿框里的注释,如果一个Object被Destroy了,那么它与null是相等的,这个规则适用于==和Equals,具体的逻辑在CompareBaseObjcets里

那么问题来了:为什么在上面的逻辑能走到obj.Equals(null)分支里而没有进入obj == null的分支呢,这是因为上面的函数是一个泛型函数:c#的泛型实现与C++的模板不一样,它不会在编译器根据你泛型的类型去生成代码,而是把T作为一个占位符生成一份模板代码,然后再在运行时去做JIT,所以在生成模板代码的时候,编译器并不知道你的T是啥,保险起见,它会使用System.Object的去判定,所以在运行时执行 obj == null判定为false(obj和null不是同一个对象),但vs知道obj时Component,调用的是UnityEngine.Object的,所以是true。但Equals不一样,它走的是虚函数的逻辑,所以在运行时可以找到UnityEngine.Objec.Equals,所以返回true。

解决

知道问题是泛型引起的,解决方案就好办了,只要加一个UnityEngine.Object的特例化函数就好了。
但同时又引出了另一个问题,PyObjectFrom里的类型有可能是数值类型,而编译器为了保险起见,同样会把obj当作对象处理,这样就会造成了一个装箱的行为,为了避免这个装箱,需要加一个where T : Struct的版本。


可以看到我们加了一个泛型约束就可以进来了

最后感谢同事的发现,大家以后可以在代码里多注意下

C#学习笔记---基础入门

泛型<T>

使用泛型能够最大限度的重用代码/保护类型安全,提高性能

泛型成员因为类型的不确定性,不能使用算术运算符/比较运算符

类型参数可以有多个,可以是编译器能够识别的任何类型

类型参数的名字不能随便起,不能重名

技术分享
 //使用泛型
       //数组类Array
       //泛型类-需要在类名前加上泛型类型
       //定义的时候需要用T泛型类型表示任意一种数据类型
       //T-Type,S/U/V 表示其他类型
       //K/V-key/value
       public class Array<T,S,U,V>
        {
            //索引器
            public T this[int index]
            {
                set
                {
                    _arr[index] = value;
                }
                get
                {
                    return _arr[index];
                }
            }
            public int Count
            {
                get
                {
                    return _count;
                }
            }
            public void  Log()
            {
                string str = "当前数组中包含" + Count + "个元素 (";
                for (int i = 0; i < Count; i++)
                {
                    str += _arr[i];
                    if (i < Count - 1)
                    {
                        str += ", ";

                    }
                }
                str += ")";
                Console.WriteLine(str);
                Console.ReadLine();

            }
            public void Add(T value)
            {
                _arr[_count] = value;
                _count++;
            }
            public Array()
            {
                _arr = new T[100];
            }
            private T[] _arr;
            private int _count = 0;
        }
        static void Main(string[] args)
        {
            //当具体需要使用的时候才需要确定实际的类型,可以将int改为其他类型
            Array<int> arr = new Array<int>();
            arr.Log();
            arr.Add(2);
            arr.Log();
            arr.Add(34);
            arr.Log();
        }
View Code

ArrayList

arraylist 属于一种集合

集合是种容器,在程序中,使用集合管理相关对象组,集合分为非泛型集合和泛型集合

技术分享

技术分享

ArrayList是一个特殊的数组,通过添加或者删除元素就可以动态改变数组长度

可以灵活地插入/删除,访问元素,不是强类型,速度跟普通的数组比要慢。ArrayList存入的数据类型系统会默认为Object类型,因此接收元素需要强制转换。

技术分享
 public static void Log(ArrayList arr)
        {
            string str = "当前数组中包含" + arr.Count + "个元素 (";
            for (int i = 0; i < arr.Count; i++)
            {
                str += arr[i];
                if (i < arr.Count - 1)
                {
                    str += ", ";

                }
            }
            str += ")";
            Console.WriteLine(str);
            Console.ReadLine();
        }

        public static void Main(string[] args)
        {
            //首先创建对象
            ArrayList arr = new ArrayList();
            Log(arr);
            //使用Add()方法添加元素,对元素类型没有限制
            arr.Add(17);
            arr.Add(4.5f);
            arr.Add("xiaoli");
            Log(arr);

            //使用下标获取指定位置的元素
            //获取当前数组中元素的数量
            int count = arr.Count;
            //使用insert方法向指定下标位置插入元素
            arr.Insert(1, "hello");
            Log(arr);
            //使用Remove方法从数组中删除指定元素
            arr.Remove(4.5f);
            Log(arr);
            //RemoveAt方法将指定下标位置删除
            arr.RemoveAt(0);
            Log(arr);
            //是否存在指定元素
            Console.WriteLine(arr.Contains("hello"));
            //清空整个数组
            arr.Clear();
            Log(arr);
        }
View Code

List

List属于泛型集合,是一种强类型列表,其方法的使用与ArrayList相似。更推荐使用List类型,因为更安全。

声明一个List对象:List<string> arr=new List<string>();//List 对元素类型有限制,一旦确定为某类型,就必须是某类型

字典

Dictionary是存储键和值的集合,属于泛型集合使用时需引入泛型集合命名空间

Dictionary是无序的,键Key是唯一的

技术分享
 public static void Main(string[] args)
        {
            //创建一个字典对象,key的类型是string,value的类型是int
            Dictionary<string, int> dic = new Dictionary<string, int>();
            //Add方法用来添加键值对
            dic.Add("laowang", 13);
            dic.Add("hello", 15);
            //删除键值对
            dic.Remove("hello");
            //清空
            dic.Clear();

            //获取当前字典中Key,value的个数
            int count = dic.Count;
            Console.WriteLine(count);

            //检查字典中是否包含指定的key
            bool b = dic.ContainsKey("xiaoming");
            //检查字典中是否包含指定的value
            bool c = dic.ContainsValue(13);

            //尝试获取指定的key所对应的value
            int s;
            bool bb = dic.TryGetValue("xiaoming", out s);
            //如果当前包含“xiaoming”这个key,则获取对应的value并保存在s中,bb=true
            //如果当前不包含“xiaomign”这个key,则s=null,bb=false

            //通过Key获取value
            int age = dic["laowang"];
            Console.WriteLine(age);

            Console.ReadLine();
        }
View Code

栈<stack>与队列<Queue>

属于泛型集合。栈遵循后进先出原则

队列遵循先进先出原则栈与队列根据需要容量自动添加

栈与队列都允许重复元素

技术分享
public static void Main(string[] args)
        {
            //
            Stack<string> s = new Stack<string>();
            int count = s.Count;
            s.Clear();
            bool a = s.Contains("xiao");
            //将元素放入栈,使用Push方法
            s.Push("xiaomign");
            s.Push("helllo");
            s.Push("tian");
            //使用Pop方法把元素出栈,后进先出

            //队列 先进先出
            Queue<string> q = new Queue<string>();
            q.Clear();
            int cou = q.Count;
            bool d = q.Contains("xiaomign");
            //添加元素
            q.Enqueue("xiaomign");
            q.Enqueue("haha");
            q.Enqueue("lala");
            //取出元素
            string s1 = q.Dequeue();
            Console.WriteLine(s1);//输出xiaoming
            Console.ReadLine();
        }
View Code

委托

委托是一种特殊的类型,用于引用方法

定义委托需要用delegate关键字

委托可以把方法当作参数来传递

委托可以使用+-运算符合并/解绑委托

技术分享
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections;

namespace test2
{
    class Program
    {
        //定义委托-访问修饰符 delegate 返回值类型 委托名(参数列表);
        public delegate void Something(string name);
        public class Student
        {
            //可以像普通类型一样当作方法参数传递
            public void Do(Something something)
            {
                //可以像普通方法一样调用
                //真正调用了方法A--方法回掉
                something(name);
            }
            public Student(string name)
            {
                this.name = name;
            }
            private string name;
        }
        public class Teacher
        {
            public void Hungry()
            {
                Student s = new Student("laowang");
                //创建委托变量
                Something a = new Something(A);
                Something b= new Something(B);
                s.Do(a+b);//委托对象可以使用+,-来绑定解除委托
            }
            public void A(string name)
            {
                Console.WriteLine("hello"+name);
                Console.ReadLine();
            }
            public void B(string name)
            {
                Console.WriteLine("你好" + name);
                Console.ReadLine();
            }
        }
        public static void Main(string[] args)
        {
            Teacher t = new Teacher();
            t.Hungry();
        }
    }
}
View Code

事件

event和delegate的关系就好像是字段和属性的关系

event会限制delegate不能够直接赋值操作,防止将委托替换掉,只能使用+=和-=来绑定或解除委托

event还限定了delegate只能在定义的类中被调用

技术分享
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections;

namespace test2
{
    class Program
    {
        
        public delegate void Something(string name);
        public class Student
        {
            //event就是delegate的“属性”
            public event Something something;
            public void Do()
            {               
                something(name);
            }
            public Student(string name)
            {
                this.name = name;
            }
            private string name;
        }
        public class Teacher
        {
            public void Hungry()
            {
                Student s = new Student("laowang");
                //使用事件后不能够直接赋值操作,防止将委托替换掉,只能使用+=和-=来绑定或解除委托             
                s.something += new Something(A);
                //使用事件后不能在Student外界调用委托-s.something("hello");
                s.Do();
            }
            public void A(string name)
            {
                Console.WriteLine("hello"+name);
                Console.ReadLine();
            }
          
        }
        public static void Main(string[] args)
        {
            Teacher t = new Teacher();
            t.Hungry();
        }
    }
}
View Code

 

以上是关于C#学习笔记--泛型函数的==和Equals(看完你一定能学到!)的主要内容,如果未能解决你的问题,请参考以下文章

C#学习笔记8

Swift学习笔记-错误处理和泛型

泛型学习笔记

C++泛型编程&模板学习笔记

C++泛型编程&模板学习笔记

C#高级编程笔记 Day 5, 9月 13日 (泛型)