主頁 > .NET開發 > C#基礎篇——事件

C#基礎篇——事件

2020-09-13 05:23:12 .NET開發

前言

在本章中,主要是借機這個C#基礎篇的系列整理過去的學習筆記、歸納總結并更加理解透徹,

在上一篇文章,我們已經對委托有了進一步了解,委托相當于用方法作為另一方法引數,同時,也可以實作在兩個不能直接呼叫的方法中做橋梁,

下面我們來回顧一下委托的例子,

    public delegate void ExecutingDelegate(string name);

    public class ExecutingManager
    {
        public void ExecuteProgram(string name, ExecutingDelegate ToExecuting)
        {
            ToExecuting(name);
        }
    }
    private static void StartExecute(string name)
    {
        Console.WriteLine("開始執行:" + name);
    }

    private static void EndExecute(string name)
    {
        Console.WriteLine("結束執行:" + name);
    }

    static void Main(string[] args)
    {
        ExecutingManager exec = new ExecutingManager();
        exec.ExecuteProgram("開始,,,", StartExecute);
        exec.ExecuteProgram("結束,,,", EndExecute);
        Console.ReadKey();
    }

根據上述的示例,再利用上節學到的知識,將多個方法系結到同一個委托變數實作多播,該如何做呢?

再次修改代碼:

    static void Main(string[] args)
    {
        ExecutingManager exec = new ExecutingManager();
        ExecutingDelegate executingDelegate;
        executingDelegate = StartExecute;
        executingDelegate += EndExecute;
        exec.ExecuteProgram("yuan", executingDelegate);

        Console.ReadKey();
    }

但是,此刻我們發現是不是可以將實體化宣告委托的變數封裝到ExecutingManager類中,這樣是不是更加方便呼叫呢?

    public class ExecutingManager
    {
        /// <summary>
        /// 在 ExecutingManager 類的內部宣告 executingDelegate 變數
        /// </summary>
        public ExecutingDelegate executingDelegate;
        public void ExecuteProgram(string name, ExecutingDelegate ToExecuting)
        {
            ToExecuting(name);
        }
    }
    static void Main(string[] args)
    {
        ExecutingManager exec = new ExecutingManager();
        exec.executingDelegate = StartExecute;
        exec.executingDelegate += EndExecute;
        exec.ExecuteProgram("yuan", exec.executingDelegate);
        Console.ReadKey();
    }

寫到這里了,這樣做沒有任何問題,但我們發現這條陳述句很奇怪,在呼叫exec.ExecuteProgram方法的時候,再次傳遞了exec的executingDelegate欄位, 既然如此,我們何不修改 ExecutingManager類成這樣:

    public class ExecutingManager
    {
        /// <summary>
        /// 在 GreetingManager 類的內部宣告 delegate1 變數
        /// </summary>
        public ExecutingDelegate executingDelegate;
        public void ExecuteProgram(string name)
        {
            if (executingDelegate != null) // 如果有方法注冊委托變數
            {
                executingDelegate(name); // 通過委托呼叫方法
            }
        }
    }
    static void Main(string[] args)
    {
        ExecutingManager exec = new ExecutingManager();
        exec.executingDelegate = StartExecute;
        exec.executingDelegate += EndExecute;
        exec.ExecuteProgram("yuan");
        Console.ReadKey();
    }

這樣再看,發現呼叫一下就更加簡潔了,

正文

在日常生活中,我們可能都會遇到這樣的各種各樣的事情,而對于這些事情我們都會采取相應的措施,比如,當你要給一個女神過生日的時候,你就可以給她送禮物,而這種情況,在C#開發中,就相當于過生日被當作事件來對待,而送禮物就是事件做出的回應,

當女神過生日的時候,女神就會發布生日事件,而你就會接受到這個事件的通知,并做出回應的處理(送禮物等騷操作),其中,觸發這個事件的物件我們可稱之為事件發布者,而捕獲這個事件并做出相應處理的稱之為事件訂閱者,我們可以看出,女神就是充當了發布者,而你自己則充當了訂閱者,

這里由生日事件引申出兩類角色,即事件發布者事件訂閱者

開始

1.發布者/訂閱者模式

在開發中,我們是否遇到這樣的情景,當一個特定的程式事件發生時,其他程式部分可以得到該事件注冊發生通知,

發布者定義一系列事件,并提供一個注冊方法;訂閱者向發布者注冊,并提供一個可被回呼的方法,也就是事件處理程式;當事件被觸發的時候,訂閱者得到通知,而訂閱者所提交的所有方法會被執行,

  • 發布者:發布某個事件的類或結構,其他類可以在該事件發生時得到通知,
  • 訂閱者:注冊并在事件發生時得到通知的類或結構,
  • 事件處理程式:由訂閱者注冊到事件的方法,在發布者觸發事件時執行,事件處理程式方法可以定義在事件所在的類或結果中,也可以定義在不同的類或結構中,
  • 觸發事件:呼叫事件的術語,當事件觸發時,所有注冊到它的方法都會被一次呼叫,

2.基本使用

        /// <summary>
        /// 先自定義一個委托
        /// </summary>
        /// <param name="oldPrice"></param>
        /// <param name="newPrice"></param>
        public delegate void PriceChangedHandler(decimal oldPrice, decimal newPrice);
        /// <summary>
        /// 這個一個發布者
        /// </summary>
        public class IPhone
        {
            decimal price;
            /// <summary>
            /// 定義一個事件
            /// event 用來定義事件
            /// PriceChangedHandler委托型別,事件需要通過委托來呼叫訂閱者需要的方法
            /// </summary>
            public event PriceChangedHandler PriceChanged;
          
            public decimal Price
            {
                get { return price; }
                set
                {
                    if (price == value)
                        return;
                    decimal oldPrice = price;
                    price = value;             // 如果呼叫串列不為空,則觸發,            
                    if (PriceChanged != null)   //用來判斷事件是否被訂閱者注冊過
                        PriceChanged(oldPrice, price);   //呼叫事件
                }
            }
        }
        /// <summary>
        /// 這個一個訂閱者
        /// </summary>
        /// <param name="oldPrice"></param>
        /// <param name="price"></param>
        static void iPhone_PriceChanged(decimal oldPrice, decimal price)
        {
            Console.WriteLine("618促銷活動,全場手機 只賣 " + price + " 元, 原價 " + oldPrice + " 元,快來搶!");
        }
        static void Main()
        {
            ///實體化一個發布者類
            IPhone phone = new IPhone()
            {
                Price = 5288
            };         // 訂閱事件   
            phone.PriceChanged += iPhone_PriceChanged;          //完成事件的注冊 調整價格(事件發生)    
            phone.Price = 3999;                                //激發事件,并呼叫事件
            Console.ReadKey();
        }

輸出:

618促銷活動,全場手機 只賣 3999 元, 原價 5288 元,快來搶!

3.決議

  1. 委托型別宣告:事件與事件處理程式必須有共同的簽名和回傳型別,它們通過委托型別進行描述,
  2. 事件宣告:使用關鍵字evet來宣告一個事件,當宣告的事件為一個public時,稱為發布了一個事件,
  3. 事件注冊:訂閱者通過+=運算子來注冊事件,并提供一個事件處理程式,
  4. 事件處理程式: 訂閱者向事件注冊的方法,它可以是顯示命名的方法、匿名方法或者Lambda運算式
  5. 觸發事件:發布者用來呼叫事件的代碼

4.語法

事件的宣告語法:

//宣告一個事件
public [static] event EventHandler EventName;
//宣告多個同型別的事件
public [static] event EventHandler EventName1, EventName2, EventName3;

事件必須宣告在類或結構中,因為事件它不是一個型別,它是一個類或者結構中的一員,

在事件被觸發之前,可以通過和null做比較,判斷是否包含事件注冊處理程式,因為事件成員被初始化默認是null,

委托型別EventHandler是宣告專門用來事件的委托,事件提供了對委托的結構化訪問;也即是無法直接訪問事件中的委托,

5.用法

img

查看原始碼:

事件的標準模式就是System命名空間下宣告的EventHandler委托型別,

EventArgs是System下的一個類,如下:

using System.Runtime.InteropServices;

namespace System
{
    [Serializable]
    [ComVisible(true)]
    [__DynamicallyInvokable]
    public class EventArgs
    {
        [__DynamicallyInvokable]
        public static readonly EventArgs Empty = new EventArgs();

        [__DynamicallyInvokable]
        public EventArgs()
        {
        }
    }
}

根據EventArgs原始碼看出,EventArgs本身無法保存和傳遞資料的,

如果想保存和傳遞資料,可以實作一個EventArgs的派生類,然后定義相關的欄位來保存和傳遞引數,

    public class IPhone
    {
        decimal price;
        /// <summary>
        /// 使用EventHandler定義一個事件
        /// </summary>
        public event EventHandler PriceChanged;
        protected virtual void OnPriceChanged()
        {
            if (PriceChanged != null)
                PriceChanged(this, null);
        }
        public decimal Price
        {
            get { return price; }
            set
            {
                if (price == value) return;
                decimal oldPrice = price; 
                price = value;             // 如果呼叫串列不為空,則觸發,      
                if (PriceChanged != null)  // //用來判斷事件是否被訂閱者注冊過
                    OnPriceChanged();
            }
        }
    }
    /// <summary>
    /// 這個一個訂閱者
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    static void iphone_PriceChanged(object sender, EventArgs e)
    {
        Console.WriteLine("年終大促銷,快來搶!");
    }
    static void Main()
    {
        IPhone phone = new IPhone()
        {
            Price = 5288M
        };         // 訂閱事件  
        phone.PriceChanged += iphone_PriceChanged;
        // 調整價格(事件發生)   
        phone.Price = 3999;
        Console.ReadKey();
    }

通過擴展EventHanlder來傳遞資料

img

System下另有泛型EventHandler類,由此,這里我們可以將派生于EventArgs的類作為型別引數傳遞過來,這樣,既可以獲得派生類保存的資料,

    ///擴展類
    public class PriceChangedEventArgs : System.EventArgs
    {
        public readonly decimal OldPrice;
        public readonly decimal NewPrice;
        public PriceChangedEventArgs(decimal oldPrice, decimal newPrice)
        {
            OldPrice = oldPrice;
            NewPrice = newPrice;
        }
    }
    public class IPhone
    {
        decimal price;
        public event EventHandler<PriceChangedEventArgs> PriceChanged;
        protected virtual void OnPriceChanged(PriceChangedEventArgs e)
        {
            if (PriceChanged != null)
                PriceChanged(this, e);
        }
        public decimal Price
        {
            get { return price; }
            set
            {
                if (price == value) return;
                decimal oldPrice = price;
                price = value;             // 如果呼叫串列不為空,則觸發,      
                if (PriceChanged != null)
                    OnPriceChanged(new PriceChangedEventArgs(oldPrice, price));
            }
        }
    }
    static void iphone_PriceChanged(object sender, PriceChangedEventArgs e)
    {
        Console.WriteLine("618促銷活動,全場手機 只賣 " + e.NewPrice + " 元, 原價 " + e.OldPrice + " 元,快來搶!");
    }
    static void Main()
    {
        IPhone phone = new IPhone()
        {
            Price = 5288M
        };         // 訂閱事件  
        phone.PriceChanged += iphone_PriceChanged;
        // 調整價格(事件發生)   
        phone.Price = 3999;
        Console.ReadKey();
    }

輸出

618促銷活動,全場手機 只賣 3999 元, 原價 5288 元,快來搶!

6.移除事件

可以利用 -= 運算子處理程式從事件中移除,當程式處理完后,可以將事件從中把它移除掉,

        class Publiser
        {
            public event EventHandler SimpleEvent;

            public void RaiseTheEvent()
            {
                SimpleEvent(this, null);
            }
        }

        class Subscriber
        {
            public void MethodA(object o, EventArgs e) { Console.WriteLine("A"); }
            public void MethodB(object o, EventArgs e) { Console.WriteLine("B"); }
        }


        static void Main(string[] args)
        {
            Publiser p = new Publiser();
            Subscriber s = new Subscriber();

            p.SimpleEvent += s.MethodA;
            p.SimpleEvent += s.MethodB;
            p.RaiseTheEvent();

            Console.WriteLine("\n移除B事件處理程式");
            p.SimpleEvent -= s.MethodB;
            p.RaiseTheEvent();

            Console.ReadKey();
        }

輸出:

img

7.事件訪問器

運算子+= 、-=事件允許的唯一運算子,這些運算子是有預定義的行為,然而,我們可以修改這些運算子的行為,讓事件執行任何我們希望定義的代碼,

可以通過為事件定義事件訪問器,來控制事件運算子+=、-=運算子的行為

  1. 兩個訪問器: add 和 remove
  2. 宣告事件的訪問器看上去和宣告一個熟悉差不多,

下面示例演示了具有訪問器的宣告.兩個訪問器都有叫做value的隱式值引數,它接受實體或靜態方法的參考

public event EventHandler Elapsed
{
    add
    {
        //... 執行+=運算子的代碼
    }

     remove
     {
        //... 執行-=運算子的代碼
     }

}

宣告了事件訪問器后,事件不包含任何內嵌委托物件.我們必須實作自己的機制來存盤和移除事件的方法,

事件訪問器表現為void方法,也就是不能使用會回傳值的return陳述句,

示例:

        //宣告一個delegate
        delegate void EventHandler();

        class MyClass
        {
            //宣告一個成員變數來保存事件句柄(事件被激發時被呼叫的delegate)
            private EventHandler m_Handler = null;

            //激發事件
            public void FireAEvent()
            {
                if (m_Handler != null)
                {
                    m_Handler();
                }
            }

            //宣告事件
            public event EventHandler AEvent
            {
                //添加訪問器
                add
                {
                    //注意,訪問器中實際包含了一個名為value的隱含引數
                    //該引數的值即為客戶程式呼叫+=時傳遞過來的delegate
                    Console.WriteLine("AEvent add被呼叫,value的HashCode為:" + value.GetHashCode());
                    if (value != null)
                    {
                        //設定m_Handler域保存新的handler
                        m_Handler = value;
                    }
                }

                //洗掉訪問器
                remove
                {
                    Console.WriteLine("AEvent remove被呼叫,value的HashCode為:" + value.GetHashCode());
                    if (value =https://www.cnblogs.com/i3yuan/p/= m_Handler)
                    {
                        //設定m_Handler為null,該事件將不再被激發
                        m_Handler = null;
                    }
                }

            }

        }

        static void Main(string[] args)
        {
            MyClass obj = new MyClass();
            //創建委托
            EventHandler MyHandler = new EventHandler(MyEventHandler);
            MyHandler += MyEventHandle2;
            //將委托注冊到事件
            obj.AEvent += MyHandler;
            //激發事件
            obj.FireAEvent();
            //將委托從事件中撤銷
            obj.AEvent -= MyHandler;
            //再次激發事件
            obj.FireAEvent();


            Console.ReadKey();
        }
        //事件處理程式
        static void MyEventHandler()
        {
            Console.WriteLine("This is a Event!");
        }

        //事件處理程式
        static void MyEventHandle2()
        {
            Console.WriteLine("This is a Event2!");
        }

輸出:

img

總結

  1. 這節對事件的基本使用,以及事件的標準語法、事件訪問器等多個地方進行說明,大致可以了解和掌握事件的基本使用,
  2. 結合上一篇的委托和這一節的事件,委托和事件我們大概掌握了基本用法,并加以實踐,結合實際開發,應用其中,
  3. 如果有不對的或不理解的地方,希望大家可以多多指正,提出問題,一起討論,不斷學習,共同進步,

參考 檔案 《C#圖解教程》

注:搜索關注公眾號【DotNet技術谷】--回復【C#圖解】,可獲取 C#圖解教程檔案

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/20491.html

標籤:C#

上一篇:C#9.0 終于來了,您還學的動嗎? 帶上VS一起解讀吧!(應該是全網第一篇)

下一篇:Asp.netCore RESTful WebApi 小結

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more