C#:单例,闭包,委托与事件,线程,Parallel,Params,扩展方法,接口与抽象类

这篇具有很好参考价值的文章主要介绍了C#:单例,闭包,委托与事件,线程,Parallel,Params,扩展方法,接口与抽象类。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

单例模式

在对泛型的约束中,最常使用的关键字有where 和 new。
其中where关键字是约束所使用的泛型,该泛型必须是where后面的类,或者继承自该类。
new()说明所使用的泛型,必须具有无参构造函数,这是为了能够正确的初始化对象

   /// <summary>
    /// C#单例模式
    /// </summary>
    public abstract class Singleton<T> where T : class,new()
    {
        private static T instance;
        private static object syncRoot = new Object();
        public static T Instance
        {
            get
            {
                if (instance == null)
                {
                    lock (syncRoot)
                    {
                        if (instance == null)
                            instance = new T();
                    }
                }
                return instance;
            }
        }

        protected Singleton()
        {
            Init();
        }

        public virtual void Init() { }
    }

1.泛型约束class Singleton where T : class,new()
2.静态对象没创建,使用new T()
3.在构造函数中可以加入虚方法
在上述示例中,我们使用泛型类型参数 T 来表示子类。where T : class, new() 约束了 T 必须是一个引用类型并且必须有一个无参构造函数。instance 变量和 Instance 属性与之前的示例相同。
当你需要扩展该单例类时,你只需创建一个继承自 Singleton 的子类,并在其中实现你的逻辑:

public class MySingleton : Singleton<MySingleton>
{
    // your code here
}

闭包陷阱

闭包是一个代码块(在C#中,指的是匿名方法或者Lambda表达式,也就是匿名函数),并且这个代码块使用到了代码块以外的变量,于是这个代码块和用到的代码块以外的变量(上下文)被“封闭地包在一起”。当使用此代码块时,该代码块里使用的外部变量的值,是使用该代码块时的值,并不一定是创建该代码块时的值。
一句话概括,闭包是一个包含了上下文环境的匿名函数。

动态给按钮回调传入参数,如果缺少int cur = i;
进入按钮的回调,按任何参数都是for循环i最后一个最大值

由于使用了 lambda 表达式作为 AddListener 的参数,变量 i 成为了被 lambda 表达式捕获的外部变量,所以变量 i 将不会被作为垃圾回收,直至引用变量的委托符合垃圾回收的条件。

i 的最终取值是 m_listContent.Count,这导致所有按钮都被使用lm_listContent.Count,和需求不符,解决方法是在每一轮循环中都定义新的变量,这样每一次 lambda 表达式都捕获了不同的变量,避免闭包陷阱。

for (int i = 0; i < m_listContent.Count; i++)
            {
                int cur = i;
                UIButton btn = m_listContent[i].GetComponent<UIButton>();
                
                btn.onClick.Add(new EventDelegate(delegate ()
                {
                    OnBtnGotoUrl(cur);
                    //错误写法OnBtnGotoUrl(i);
                }));
            }

委托与事件

① 委托把一个方法作为参数代入另外一个方法,理解为函数指针
② 触发委托有2种方式: 委托实例.Invoke(参数列表),委托实例(参数列表)
③ 事件可以看作是一个委托类型的变量
④ 通过+=为事件注册多个委托实例或多个方法
⑤ 通过-=为事件注销多个委托实例或多个方法

delegate 是为了在C#中把函数作为对象传来传去而实现的一个“函数包装”,委托是具有相同签名的函数(方法)的类型。事件是委托的应用方式之,事件是一个属性/字段,类型是委托

delegate除了使用+=或-=来监听和移除方法,还可以用=,这样子使用会不小心把监听列表都覆盖掉的。
而event规范化了只能用+=和-=。

IDisposable

using

在 C# 中,using 语句是用于包裹一个实现 IDisposable 接口的对象的常见方式。IDisposable 接口提供了一种在使用完对象后释放资源的机制。
以下是一些常见的情况,在这些情况下你可以使用 using 语句来包裹对象:
1.文件操作:当你使用 FileStream、StreamReader、StreamWriter 等类进行文件读写时,通常会使用 using 来确保文件流在使用完后被正确关闭和释放资源。

using (FileStream fileStream = new FileStream("example.txt", FileMode.Open))
{
    // 使用文件流进行读写操作
}

2.数据库连接:当你使用 SqlConnection、SqlCommand、SqlDataReader 等类与数据库进行交互时,同样可以使用 using 来自动释放数据库连接和相关资源。

using (SqlConnection connection = new SqlConnection(connectionString))
{
    // 打开数据库连接并执行查询操作
}

3.网络请求:当你使用 HttpClient 或其他网络请求相关的类时,可以使用 using 来确保网络连接在使用完后被正确关闭。

using (HttpClient client = new HttpClient())
{
    // 发起网络请求
}

4.其他资源管理:任何实现了 IDisposable 接口的对象,如果需要在使用完后释放资源,都可以使用 using 语句来包裹。

using (SomeDisposableObject obj = new SomeDisposableObject())
{
    // 使用 obj 对象
}

使用 using 语句可以确保在代码块结束后,对象的 Dispose() 方法会被调用,从而释放资源。这样可以避免手动调用 Dispose() 方法或忘记释放资源的问题。

多次调Dispose

一个类型的Dispose方法应该允许被多次调用而不抛出异常。鉴于此,类型内部维护了一个私有的bool变量disposed,如下:

private bool m_Disposed;
/// <summary>
            /// 释放资源。
            /// </summary>
            public void Dispose()
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }

            /// <summary>
            /// 释放资源。
            /// </summary>
            /// <param name="disposing">释放资源标记。</param>
            private void Dispose(bool disposing)
            {
                if (m_Disposed)
                {
                    return;
                }

                if (disposing)
                {
                    if (m_FileStream != null)
                    {
                        m_FileStream.Dispose();
                        m_FileStream = null;
                    }
                }

                m_Disposed = true;
            }

GC.SuppressFinalize(this);
GC.SuppressFinalize 方法是用来通知垃圾回收器不要调用对象的析构函数(Finalize 方法)。它的作用是在对象已经被正确释放的情况下,避免不必要的资源回收操作,提高性能。
在 C# 中,当一个对象具有析构函数(Finalize 方法)时,垃圾回收器会在对象被垃圾回收之前调用该析构函数,以确保对象的资源得到正确释放。然而,在某些情况下,如果对象已经被显式地释放了,并且不再需要通过析构函数来释放资源,就可以使用 GC.SuppressFinalize 来通知垃圾回收器跳过对析构函数的调用。

不要创建过多线程

错误地创建过多线程的一个典型的例子是:为每一个Socket连接建立一个线程去管理。每个连接一个线程,意味着在32位系统的服务器不能同时管理超过约1000台的客户机。CLR为每个线程分配的内存会超过1MB。约1000个线程,加上.NET进程启动本身所占用的一些内存,即刻就耗尽了系统能分配给进程的最大可用地址空间2GB。即便应用程序在设计之初的需求设计书中说明,生产环境中客户端数目不会超过500台,在管理这500台客户端时进行线程上下文切换,也会损耗相当多的CPU时间。这类I/O密集型场合应该使用异步去完成

Parallel并行执行

在命名空间System.Threading.Tasks中,有一个静态类Parallel简化了在同步状态下的Task的操作。Parallel主要提供3个有用的方法:For、ForEach、Invoke。

static void Main(string[] args)  
{  
    int[] nums = new int[] { 1, 2, 3, 4 };  
    Parallel.For(0, nums.Length, (i) =>
    {  
        Console.WriteLine("针对数组索引{0}对应的那个元素{1}的一些工作代码……",i,  
            nums[i]);  
    });  
    Console.ReadKey();  
}

由于所有的任务都是并行的,所以它不保证先后次序。

Params传入参数

在 C# 中,使用 params 关键字作为函数参数传递不会直接导致垃圾回收(GC)。params 关键字所表示的参数数组是在编译期间就已经确定了大小并在运行时被创建的,不会引发额外的内存分配和释放操作。
当你调用带有 params 参数的函数时,编译器会将参数列表转换为一个数组,并将该数组传递给函数。这个数组在函数执行期间会存在于堆栈中,并在函数调用完成后被销毁。这个过程不会产生垃圾回收的开销。
然而,如果你在函数内部对 params 参数数组进行频繁的添加、插入、删除或修改等操作,这些操作可能会导致内存重新分配和释放,从而间接地增加垃圾回收的开销。因此,在设计代码时,应该尽量避免对 params 参数数组进行频繁的修改操作,或者考虑使用其他数据结构来替代 params 参数数组。
总的来说,params 参数本身不会直接产生垃圾回收,但如果在函数内部涉及到频繁的修改操作,可能会间接地增加垃圾回收的开销。因此,在设计和使用代码时,需要注意避免这些问题的出现。
还是有点难用,还是老实写多个函数重载吧

扩展方法

扩展方法除了让调用着可以像调用类型自身的方法一样去调用扩展方法外,它还有一些其他的主要优点:
可以扩展密封类型;
可以扩展第三方程序集中的类型;
扩展方法可以避免不必要的深度继承体系。
扩展方法还有一些必须遵循的要求:
扩展方法必须在静态类中,而且该类不能是一个嵌套类;
扩展方法必须是静态的;
扩展方法的第一个参数必须是要扩展的类型,而且必须加上this关键字;
不支持扩展属性、事件。
常见运用,C#中写设置Transform位置的扩展方法,给Lua调用,防止Lua传递Vector3造成性能消耗与类型转换

    public static void SetLocalPosition(this Transform transform, float x, float y, float z)
    {
        transform.localPosition = new Vector3(x, y, z);
    }

接口与抽象类

接口和抽象类有一些显而易见的区别:
1.接口支持多继承,抽象类则不能。
2.接口可以包含方法、属性、索引器、事件的签名,但不能有实现,抽象类则可以。
3.接口在增加新方法后,所有的继承者都必须重构,否则编译不通过,而抽象类则不需要。
这些区别导致两者的应用场景不同:
1.如果对象存在多个功能相近且关系紧密的版本,则使用抽象类。
2.如果关系不紧密,但若干功能拥有共同的声明,则使用接口。
3.抽象类适合于提供丰富功能的场合,接口则更倾向于提供单一的一组功能。
从某种角度来看,抽象类比接口更具备代码的重用性。子类无须编写代码即可具备一个共性的行为。
采用抽象类的另一个好处是,如果为为基类增加一个方法,则继承该基类的所有子类自然就会具备这个额外的方法,而接口却不能。如果接口增加一个方法,必须修改所有的子类。所以,接口一旦设计出来就应该是不变的。抽象类则可以随着版本的升级增加一些功能。
接口的作用更倾向于说明类型具有某个或者某种功能。接口只负责声明,而抽象基类往往还要负责实现。
接口的职责必须单一,在接口中的方法应该尽可能的简练。

用多态代替条件语句

    abstract class Commander
    {
        public abstract void Execute();
    }

class StartCommander : Commander
    {

        public override void Execute()
        {
            //启动
        }
    }

    class StopCommander : Commander
    {

        public override void Execute()
        {
            //停止
        }
    }

static void Main(string[] args)
        {
            Commander commander = new StartCommander();
            Drive(commander);
            commander = new StopCommander();
            Drive(commander);
        }

        static void Drive(Commander commander)
        {
            commander.Execute();
        }

将类型标识为sealed

sealed能够阻止类型被其他类型继承

使用事件访问器替换公开的事件成员变量

public class MyClass 
{ 
    // 声明事件
    private event EventHandler myEvent; 

    // 定义事件访问器
    public event EventHandler MyEvent 
    { 
        add { myEvent += value; } 
        remove { myEvent -= value; } 
    }

    // 触发事件的方法
    protected virtual void OnMyEvent() 
    { 
        EventHandler handler = myEvent; 
        if (handler != null) 
        { 
            handler(this, EventArgs.Empty); 
        } 
    } 
}

在上面的示例中,我们首先声明了一个私有的事件成员变量 myEvent,然后定义了一个公开的事件访问器 MyEvent。通过这个事件访问器,我们可以将事件添加到或从事件列表中删除事件。
在类中,使用 OnMyEvent() 方法来触发事件。该方法首先检查事件处理程序是否为空,如果不为空,则触发事件。文章来源地址https://www.toymoban.com/news/detail-795570.html

到了这里,关于C#:单例,闭包,委托与事件,线程,Parallel,Params,扩展方法,接口与抽象类的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C#委托和事件简单复习

    太久没用了,简单的复习一下 快速过一遍语法使用 1.定义一个委托类型 只需要在声明的前面加上delegate,其他的就跟声明一个方法(函数)类似 2.使用刚刚声明的委托 需要定义一个返回值跟参数与我们刚刚定义的委托一致 3.然后声明一个SayHello类型(委托)的变量 把

    2024年02月14日
    浏览(43)
  • C# Task Parallel Library (TPL) 访问线程池

    //在C#中,可以使用Task Parallel Library (TPL)来访问线程池。 //TPL是一个用于并行编程的框架,它提供了一种简单的方式来创建并行任务, //并 自动利用线程池 来执行这些任务。 //下面是一个使用TPL访问线程池的示例: //csharp using System; using System.Threading.Tasks; class Program {     st

    2024年02月08日
    浏览(76)
  • C# 中的委托和事件机制

    在C#中,委托和事件是非常重要的概念,用于实现程序中的回调和事件处理。在这里,我们将介绍C#中委托和事件机制的基础知识和用法。 委托是一种类似于C/C++函数指针的概念,它允许将方法作为参数传递到其他方法中,以实现回调函数的功能。委托是一种类型,它可以表示

    2023年04月21日
    浏览(34)
  • C#学习,委托,事件,泛型,匿名方法

    目录 委托 声明委托 实例化委托 委托的多播 委托的用途 事件 通过事件使用委托 声明事件 泛型 泛型的特性 泛型方法 泛型的委托 匿名方法 编写匿名方法的语法 类似于指针,委托是存有对某个方法的引用的一种引用类型变量,引用可以在运行时被改变。特别用于实现事件和

    2024年02月12日
    浏览(43)
  • C#学习笔记8:接口、委托、事件

    今日继续我的C#学习之路,今日学习接口、委托、事件,文章从实践出发学习这三个设计理念,并提供完整源码 目录 1、接口(多重继承): 代码: 运行结果: 2、委托(方法的代理/函数指针): 创建控制台程序实现委托: 遇到的报错及解决: 修改后的代码: 运行结果: 3、

    2024年04月15日
    浏览(53)
  • C# 事件和委托的区别并说明

    事件是基于委托的,为委托提供了一个发布/订阅机制。可以说事件是一种特殊的委托,他的调用和委托是一样的。 事件的声明 public event 委托类型 事件名称 通常事件的命名以事件名称+Event来命名。如public event delegate NotifyEvent; 事件和委托的区别如下: 事件只能在方法的外部

    2024年02月07日
    浏览(38)
  • C# 一个完整的委托、事件学习示例

    该示例符合委托、事件的定义规则,并且可以帮助大家更好地理解委托和事件的使用! 先定义了一个名为 MyEventArgs 的类,继承自 EventArgs ,它包含一个 Message 属性,用于存储传递的消息。 Publisher 类中添加了一个 protected virtual 的方法 OnMyEvent ,用于触发 MyEvent 事件,并传递

    2024年02月07日
    浏览(40)
  • 【C# Programming】委托和lambda表达式、事件

    目录 一、委托和lambda表达式 1.1 委托概述 1.2 委托类型的声明 1.3 委托的实例化 1.4 委托的内部机制  1.5 Lambda 表达式 1.6 语句lambda 1.7 表达式lambda 1.8 Lambda表达式 1.9 通用的委托 1.10 委托没有结构相等性 1.11 Lambda表达式和匿名方法的内部机制 1.12 外部变量 1.13 外部变量的

    2024年02月06日
    浏览(45)
  • 【C#进阶】C#中的委托、事件、回调函数、匿名函数和lambda表达式

    委托是一种类型,它可以存储对一个或多个方法的引用。它类似于C/C++中的函数指针,允许您将方法作为参数传递、存储和调用。 写法: delegate return_type delegate_name( ); return_type :表示委托所引用方法的返回类型。 delegate_name :表示委托的名称。 parameters :表示委托所引用方法

    2024年02月06日
    浏览(56)
  • C#学习笔记--数据结构、泛型、委托事件等进阶知识点

    ArrayList 元素类型以Object类型存储,支持增删查改的数组容器。 因而存在装箱拆箱操作,谨慎使用。 ArrayList和数组区别? ArrayList使用不用说明固定长度,数组则需要 数组存储的是指定类型的,ArrayList是Object ArrayList存在装箱拆箱,数组不存在 ArrayList数组长度用Count获取 而数组

    2024年02月08日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包