第8章:委托、Lambda表達式和事件

  • #1. 委托
    • 1.1 聲明委托
    • 1.2 使用委托
    • 1.3 簡單委托示例
    • 1.4 Action<T>和Func<T>委托
    • 1.5 BubbleSorter示例
    • 1.6 多播委托
    • 1.7 匿名方法
  • #2. Lambda 表達式
    • 2.1 參數(shù)
    • 2.2 多行代碼
    • 2.3 Lambda表達式外部變量
  • #3. 事件
    • 3.1 事件發(fā)布程序
    • 3.2 事件偵聽器
    • 3.3 弱事件

#1. 委托

當要把方法傳遞給其他方法時歹河,需要使用委托掩浙。把方法的細節(jié)封裝在一種新類型的對象中,即委托秸歧。委托只是一種特殊類型的對象厨姚,其特殊之處在于,我們以前定義的所有對象都包含數(shù)據(jù)键菱,而委托包含的只是一個或多個方法的地址谬墙。

1.1 聲明委托

在C#中使用一個類時,分兩個階段。首先拭抬,需要定義這個類部默,即告訴編譯器這個類由什么字段和方法組成。然后(除非只使用靜態(tài)方法)造虎,實例化類的一個對象傅蹂。使用委托時,也需要經(jīng)過這兩個步驟算凿。

  1. 首先必須定義要使用的委托份蝴,對于委托,定義它就是告訴編譯器這種類型的委托表示哪種類型的方法氓轰。
  2. 然后搞乏,必須創(chuàng)建該委托的一個或多個實例。編譯器在后臺將創(chuàng)建表示該委托的一個類戒努。
    定義委托的語法如下:
delegate void IntMethodInvoker(int x);

在定義委托時,必須給出它所表示的方法的簽名和返回類型等全部細節(jié)镐躲。

==理解委托的一種好方式是把委托當作這樣一件事情储玫,它給方法的簽名和返回類型指定名稱。==

1.2 使用委托

下面的代碼說明了如何使用委托萤皂。

private delegate string GetAString();

static void Main(string[] args)
{
    int x = 40;
    GetAString firstStringMethod = new GetAString(x.ToString);
    Console.WriteLine("String is {0}", firstStringMethod());
    //With firstStringMethod initialized to x.ToString(),
    //the above statement is equivalent to saying
    //Console.WriteLine("String is {0}",x.ToString());
}

在這段代碼中撒穷,實例化了類型GetAString的一個委托,并對它進行初始化裆熙,使它引用整形變量x的ToString()。在C#中,委托在語法上總是接受一個參數(shù)的構(gòu)造函數(shù)恕洲,這個參數(shù)就是委托引用的方法晒夹。這個方法必須匹配最初定義委托時的簽名。所以在這個示例中僚稿,如果不帶參數(shù)并返回一個字符串的方法來初始化firstStringMethod變量凡桥,就會產(chǎn)生一個編譯錯誤。注意蚀同,因為int.ToString()是一個實例方法(不是靜態(tài)方法)缅刽,所以需要指定實例(x)和方法名來正確地初始化委托。

實際上蠢络,給委托實例提供圓括號與調(diào)用委托類的Invoke()方法完全相同衰猛。因為firstStringMethod是委托類型的一個變量,所以c#編譯器會用firstStringMethod.Invoke()代替firstStringMethod()刹孔。

firstStringMethod();
firstStringMethod.Invoke();

為了減少輸入量啡省,只要需要委托實例,就可以只傳送地址的名稱。這稱為委托推斷冕杠。只要編譯器可以把委托實例解析為特定的類型微姊,這個C#特性就是有效的。下面用GetAString委托的一個新實例初始化GetAString類型的firstStringMethod變量:

GetAString firstStringMethod = new GetAString(x.ToString);

只要用變量x把方法名傳送給變量firstStringMethod分预,就可以編寫出作用相同的代碼:

GetAString firstStringMethod = x.ToString;

==調(diào)用上述方法時輸入形式不能為x.ToString()(不要輸入圓括號)兢交,也不能把它傳送給委托變量,輸入圓括號調(diào)用一個方法笼痹,調(diào)用x.ToString()方法會返回一個不能賦予委托變量的字符串對象配喳,只能把方法的地址賦予委托變量。==

委托推斷可以在需要委托實例的任何地方使用凳干。委托推斷也可以用于事件晴裹,因為事件基于委托。

==給委托的實例可以引用任何類型的任何對象上的實例方法或靜態(tài)方法——只要方法的簽名匹配于類型的簽名即可救赐。==

1.3 簡單的委托示例

在這個示例中涧团,定義一個類MathsOperations,它有兩個靜態(tài)方法经磅,對double類型的值執(zhí)行兩個操作泌绣,然后使用該委托調(diào)用這些方法。

class MathOperations
{
    public static double MultiplyByTwo(double value)
    {
        return value * 2;
    }

    public static double Square(double value)
    {
        return value * value;
    }
}

下面調(diào)用方法:

class DelegateTest
{
    private delegate double DoubleOp(double x);

    static void Main(string[] args)
    {
        DoubleOp[] operations = {
            MathOperations.MultiplyByTwo,
            MathOperations.Square
        };

        for (int i = 0; i < operations.Length; i++)
        {
            Console.WriteLine("Using operations[{0}]:", i);
            ProcessAndDisplayName(operations[i], 2.0);
            ProcessAndDisplayName(operations[i], 7.94);
            ProcessAndDisplayName(operations[i], 1.414);
            Console.WriteLine();
        }
    }

    static void ProcessAndDisplayName(DoubleOp action, double value)
    {
        double result = action(value);
        Console.WriteLine("Value is {0},result of operation is {1}", value, result);
    }
}

在這段代碼中预厌,實例化了一個委托數(shù)組DoubleOp(記住阿迈,一旦定義了委托類,基本上就可以實例化它的實例轧叽,就像處理一般的類那樣——所以把一些委托的實例放在數(shù)組中是可以的)苗沧。該數(shù)組的每個元素都初始化為由MathsOperations類實現(xiàn)的不同操作。

1.4 Action<T>和Func<T>委托

除了為每個參數(shù)和返回類型定義一個新委托類型之外炭晒,還可以使用Action<T>和Func<T>委托待逞。泛型Action<T>委托表示引用一個void返回類型的方法。

Func<double, double>[] operations = {
    MathOperations.MultiplyByTwo,
    MathOperations.Square
};
            
static void ProcessAndDisplayName(Func<double,double> action, double value)
{
    double result = action(value);
    Console.WriteLine("Value is {0},result of operation is {1}", value, result);
}

1.5 BubbleSorter示例

下面的示例將說明委托的真正用途网严。我們要編寫一個類BubbleSorter飒焦,它實現(xiàn)一個靜態(tài)方法Sort(),這個方法的第一個參數(shù)是一個對象數(shù)組屿笼,把該數(shù)組按照升序重新排列牺荠。

對于接受類型T的泛型方法Sort<T>,需要一個比較方法驴一,其兩個參數(shù)的類型是T休雌,if比較的返回類型是布爾類型。這個方法可以從Func<T1,T2,TResult>委托中引用肝断,其中T1和T2的類型相同:Func<T,bool>杈曲。給Sort<T>方法指定下述簽名:

static public void Sort<T>(IList<T> sortArray,Func<T,T,bool> comparison);

這個方法的文檔說明驰凛,comparison必須引用一個方法,該方法帶有兩個參數(shù)担扑,如果第一個參數(shù)的值“小于”第二個參數(shù)恰响,就返回true。

設置完畢后涌献,下面定義BubbleSorter類:

class BubbleSorter
{
    static public void Sort<T>(IList<T> sortArray, Func<T, T, bool> comparison)
    {
        bool swapped = true;
        do
        {
            swapped = false;
            for (int i = 0; i < sortArray.Count - 1; i++)
            {
                if (comparison(sortArray[i + 1], sortArray[i]))
                {
                    T tmp = sortArray[i];
                    sortArray[i] = sortArray[i + 1];
                    sortArray[i + 1] = tmp;
                    swapped = true;
                }
            }
        } while (swapped);
    }
}

為了使用這個類胚宦,需要定義另一個類,從而建立要排序的數(shù)組燕垃。

class Employee
{
    public string Name { get; private set; }
    public decimal Salary { get; private set; }
    public Employee(string name, decimal salary)
    {
        this.Name = name;
        this.Salary = salary;
    }

    public override string ToString()
    {
        return string.Format("{0}, {1:C}", Name, Salary);
    }

    public static bool CompareSalary(Employee l, Employee r)
    {
        return l.Salary < r.Salary;
    }
}

注意枢劝,為了匹配Func<T,T,bool>委托的簽名,在這個類中必須定義CompareSalary卜壕,它的參數(shù)是兩個Employee引用您旁,并返回一個布爾值,在實現(xiàn)比較代碼中轴捎,根據(jù)薪水進行比較鹤盒。

下面編寫一些客戶端代碼,完成排序:

class DelegateTest
{
    static void Main(string[] args)
    {
         Employee[] employees = {
            new Employee("Bugs Bunny",20000),
            new Employee("Elmer Fudd",10000),
            new Employee("Daffy Duck",25000),
            new Employee("Will Coyote",1000000.38m),
            new Employee("Foghorn Leghorn",23000),
            new Employee("RoadRunner",50000),
        };
        BubbleSorter.Sort(employees, Employee.CompareSalary);

        foreach(var employee in employees)
        {
            Console.WriteLine(employee);
        }
    }
}

1.6 多播委托

前面使用的每個委托都只包含一個方法調(diào)用侦副。調(diào)用委托的次數(shù)與調(diào)用方法的次數(shù)相同昨悼。如果要調(diào)用多個方法,就需要多次顯式調(diào)用這個委托跃洛。但是,委托也可以包含多個方法终议。這種委托稱為多播委托汇竭。如果調(diào)用多播委托,就可以按順序連續(xù)調(diào)用多個方法穴张。為此细燎,委托的簽名就必須返回void;否則皂甘,就只能得到委托調(diào)用的最后一個方法的結(jié)果玻驻。

可以使用返回類型為void的Action<double>委托:

class DelegateTest
{
    static void Main(string[] args)
    {
        Action<double> operations = MathOperations.MultiplyByTwo;
        operations += MathOperations.Square;
    }    
}

在前面的示例中,因為要存儲對兩個方法的引用偿枕,所以實例化了一個委托數(shù)組璧瞬。而這里只是在同一個多播委托中添加兩個操作。多播委托可以識別運算符"+"和"+="渐夸。

要委托引用返回void的方法嗤锉,就應重寫MathOperations類中的方法,從而讓它們顯示其結(jié)果墓塌,而不是返回它們:

class MathOperations
{
    public static void MultiplyByTwo(double value)
    {
        double result = value * 2;
        Console.WriteLine("Multiplying by 2: {0} gives {1}", value, result);
    }

    public static void Square(double value)
    {
        double result = value * value;
        Console.WriteLine("Squaring: {0} gives {1}", value, result);
    }
}

為了適應這個改變瘟忱,也必須重寫ProcessAndDisplayName()方法:

static void ProcessAndDisplayName(Action<double> action, double value)
{
    Console.WriteLine();
    Console.WriteLine("ProcessAndDisplayName called with value = {0}", value);
    action(value);
}

下面測試多播委托奥额,其代碼如下:

class DelegateTest
{
    static void Main(string[] args)
    {
        Action<double> operations = MathOperations.MultiplyByTwo;
        operations += MathOperations.Square;
        
        ProcessAndDisplayName(operations, 2.0);
        ProcessAndDisplayName(operations, 7.94);
        ProcessAndDisplayName(operations, 1.414);
        Console.WriteLine();    
    }    
}

如果正在使用多播委托,就應知道對同一個委托調(diào)用方法鏈的順序并未正式定義访诱。因此應避免編寫依賴于以特定順序調(diào)用方法的代碼垫挨。

通過一個委托調(diào)用多個方法還可能導致一個大問題。多播委托包含一個逐個調(diào)用的委托集合触菜。如果通過委托調(diào)用的其中一個方法拋出異常九榔,整個迭代就會停止。

class DelegateTest
{
    static void Main(string[] args)
    {
        Action d1 = One;
        d1 += Two;

        try
        {
            d1();
        }
        catch (Exception e)
        {
            Console.WriteLine("Exception caught with msg:{0}",e.Message);
        }
    }
    
    static void One()
    {
        Console.WriteLine("One");
        throw new Exception("Error in one");
    }
    
    static void Two()
    {
        Console.WriteLine("Two");
    }
}

委托只調(diào)用了第一個方法玫氢。因為第一個方法拋出了異常帚屉,所以委托的迭代會停止,不再調(diào)用Two()漾峡。

在這種情況下攻旦,為了避免這個問題,應自己迭代方法列表生逸。Delegate類定義GetInvocationList()牢屋,它返回一個Delegate數(shù)組。現(xiàn)在可以使用這個委托調(diào)用與委托直接相關(guān)的方法槽袄,捕獲異常烙无,并繼續(xù)下一次迭代。

class DelegateTest
{
    static void Main(string[] args)
    {
        Action d1 = One;
        d1 += Two;

        Delegate[]  delegates = d1.GetInvocationList();
        foreach(Action d in delegates)
        {
            try
            {
                d();
            }
            catch (Exception e)
            {
                Console.WriteLine("Exception caught with msg:{0}", e.Message);
            }
        }
    }
}

1.7 匿名方法

到目前為止遍尺,要想使委托工作截酷,方法必須已經(jīng)存在(即委托是用它將調(diào)用的方法的相同的簽名定義的)。但還有另外一種使用委托的方式:即通過匿名方法乾戏。匿名方法是用作委托的參數(shù)的一段代碼迂苛。
用匿名方法定義委托的語法與前面的定義并沒有區(qū)別。但在實例化委托時鼓择,就有區(qū)別了三幻。

class DelegateTest
{
    static void Main(string[] args)
    {
        string mid = ", middle part,";
        Func<string, string> anonDel = delegate (string param)
        {
            param += mid;
            param += " and this was added to the string.";
            return param;
        };
        Console.WriteLine(anonDel("Start of string"));
    }
}

Func<string,string>委托接受一個字符串參數(shù),返回一個字符串呐能。anonDel是這種委托類型的變量念搬。不是把方法名賦予這個變量,而是使用一段簡單的代碼:它前面是關(guān)鍵字delegate摆出,后面是一個字符串參數(shù)朗徊。

在使用匿名方法時,必須遵循兩條規(guī)則偎漫。在匿名方法中不能使用跳轉(zhuǎn)語句(break荣倾、goto或continue)跳到該匿名方法的外部,反之亦然:匿名方法外部的跳轉(zhuǎn)語句不能跳到該匿名方法的內(nèi)部骑丸。

在匿名方法內(nèi)部不能訪問不安全的代碼舌仍。另外妒貌,也不能訪問在匿名方法外部使用的ref或out參數(shù)。但可以使用在匿名外部定義的其他變量铸豁。


#2. Lambda表達式

自C#3.0開始灌曙,就可以使用一種新語法把實現(xiàn)代碼賦予委托:Lambda表達式。只要有委托參數(shù)類型的地方节芥,就可以使用Lambda表達式在刺。前面使用匿名方法的例子可以改為使用Lambda表達式:

class LambdaTest
{
    static void Main(string[] args)
    {
        string mid = ", middle part,";
        Func<string, string> lambda = param => 
        {
            param += mid;
            param += " and this was added to the string.";
            return param;
        };
        Console.WriteLine(lambda("Start of string"));
    }
}

Lambda運算符"=>"的左邊列出了需要的參數(shù)。Lambda運算符的右邊定義了賦予lambda變量的方法的實現(xiàn)代碼头镊。

2.1 參數(shù)

Lambda表達式有幾種定義參數(shù)的方式蚣驼。如果只有一個參數(shù),只寫出參數(shù)名字就足夠了相艇。下面的Lambda表達式使用了參數(shù)s颖杏。因為委托類型定義了一個string參數(shù),所以s的類型就是string坛芽。

Func<string,string> oneParam = s => String.Format(
    "change uppercase {0}",s.ToUpper());
Console.WriteLine(oneParam("test"));

如果委托使用多個參數(shù)留储,就把參數(shù)名放在花括號中。這里參數(shù)x和y的類型是double咙轩,由Func<double,double,double>委托定義:

Func<double,double,double> twoParams = (x,y) => x * y;
Console.WriteLine(twoParams(3,2));

為了方便获讳,可以在花括號中給變量名添加參數(shù)類型:

Func<double, double, double> twoParamsWithTypes = (double x, double y) => x * y;
Console.WriteLine(twoParamsWithTypes(4, 2));

2.2 多行代碼

如果Lambda表達式只有一條語句,在方法塊內(nèi)就不需要花括號和return語句活喊,因為編譯器會添加一條隱式的return語句丐膝。

Func<double,double> square = x => x * x;

添加花括號、return語句和分號是完全合法的钾菊,通常這比不添加這些符號更容易閱讀:

Func<double,double> square = x => {
    return x * x;
}

但是帅矗,如果在Lambda表達式的實現(xiàn)代碼中需要多條語句,就必須添加花括號和return語句:

Func<string,string> lambda = param => {
    param += mid;
    param += " and this was added to the string";
    return param;
}

2.3 Lambda表達式外部的變量

通過Lambda表達式可以訪問Lambda表達式塊外部的變量结缚。這是一個非常好的功能,但如果為正確使用软棺,也會非常危險红竭。

在下面的示例中,F(xiàn)unc<int,int>類型的Lambda表達式需要一個int參數(shù)喘落,返回一個int茵宪。該Lambda表達式的參數(shù)用變量x定義。實現(xiàn)代碼還訪問了Lambda表達式外部的變量someVal瘦棋。只要不認為在調(diào)用f時稀火,Lambda表達式創(chuàng)建了一個以后使用的新方法,這似乎沒有什么問題赌朋。

int someVal = 5;
Func<int, int> f = x => x + someVal;
Console.WriteLine(f(10));

對于Lambda表達式x => x + someVal,編譯器會創(chuàng)建一個匿名類凰狞,它有一個構(gòu)造函數(shù)來傳遞外部變量篇裁。該構(gòu)造函數(shù)取決于從外部傳遞進來的變量個數(shù)。

public class AnonymousClass 
{
    private int someVal;
    public AnonymousClass(int someVal) 
    {
        this.someVal = someVal;
    }
    
    public int AnonymouseMethod(int x) {
        return x + someVal;
    }
}

使用Lambda表達式并調(diào)用該方法赡若,會創(chuàng)建匿名類的一個實例达布,并傳遞調(diào)用該方法時變量的值。

==Lambda表達式可以用于類型是一個委托的任意地方逾冬。類型是Expression或Expression<T>時黍聂,也可以使用Lambda表達式。此時編譯器會創(chuàng)建一個表達式樹身腻。==


#3. 事件

事件基于委托产还,為委托提供了一種發(fā)布/訂閱機制。

3.1 事件發(fā)布程序

從CarDealer類開始嘀趟,它基于事件提供一個訂閱脐区。CarDealer類用event關(guān)鍵字定義了類型為EventHandler<CarInfoEventArgs>的NewCarInfo事件。在NewCar()方法中去件,觸發(fā)NewCarInfo事件:

public class CarIInfoEventArgs : EventArgs
{
    public string Car { get; private set; }
    public CarIInfoEventArgs(string car)
    {
        this.Car = car;
    }
}

public class CarDealer
{
    public event EventHandler<CarIInfoEventArgs> NewCarInfo;

    public void NewCar(string car)
    {
        Console.WriteLine("CarDealer, new car {0}", car);
        NewCarInfo?.Invoke(this, new CarIInfoEventArgs(car));
    }
}

CarDealer類提供了EventHandler<CarIInfoEventArgs>類型的NewCarInfo事件坡椒。作為一個約定,事件一般使用帶兩個參數(shù)的方法尤溜,其中第一個參數(shù)是一個對象倔叼,包含事件的發(fā)送者,第二個參數(shù)提供了事件的相關(guān)信息宫莱。

委托EventHandler<TEventArgs>的定義如下:

public delegate void EventHandler<TEventArgs>(object sender,TEventArgs e)
    where TEventArgs : EventArgs

3.2 事件偵聽器

Consumer類用作事件偵聽器丈攒。這個類訂閱了CarDealer類的事件,并定義了NewCarIsHere方法授霸,該方法滿足EventHandler<CarInfoEventArgs>委托的要求巡验,其參數(shù)類型是object和CarInfoEventArgs:

public class Consumer
{
    private string name;

    public Consumer(string name)
    {
        this.name = name;
    }

    public void NewCarIsHere(object sender, CarIInfoEventArgs e)
    {
        Console.WriteLine("{0}: car {1} is new", name, e.Car);
    }
}

現(xiàn)在需要連接事件發(fā)布程序和訂閱器。為此使用CarDealer類的NewCarInfo事件碘耳,通過“+=”創(chuàng)建一個訂閱显设。消費者訂閱了事件,接著消費者也訂閱了事件辛辨,然后通過“-=”取消了訂閱捕捂。

static void Main(string[] args)
{
    ar dealer = new CarDealer();

    var michael = new Consumer("Michael");
    dealer.NewCarInfo += michael.NewCarIsHere;

    dealer.NewCar("Mercedes");

    var nick = new Consumer("Nick");
    dealer.NewCarInfo += nick.NewCarIsHere;

    dealer.NewCar("Ferrari");

    dealer.NewCarInfo -= michael.NewCarIsHere;
    dealer.NewCar("Toyota");
}

3.3 弱事件

通過事件,直接連接到發(fā)布程序和偵聽器斗搞。但垃圾回收有一個問題指攒。例如,如果偵聽器不再直接飲用僻焚,發(fā)布程序就仍有一個引用允悦。垃圾回收器不能清空偵聽器占用的內(nèi)存,因為發(fā)布程序仍保有一個引用虑啤,會針對偵聽器觸發(fā)事件隙弛。

這種強連接可以通過弱事件模式來解決架馋,即使用WeekEventManager作為發(fā)布程序和偵聽器之間的中介。

1. 弱事件管理器

要使用弱事件驶鹉,需要創(chuàng)建一個派生自WeekEventManager類的類绩蜻。WeekEventManager類

 public class WeakCarInfoEventManager : WeakEventManager
{
    public static WeakCarInfoEventManager CurrentManager
    {
        get
        {
            WeakCarInfoEventManager manager = GetCurrentManager(typeof(WeakCarInfoEventManager))
                        as WeakCarInfoEventManager;
            if (manager == null)
            {
                manager = new WeakCarInfoEventManager();
                SetCurrentManager(typeof(WeakCarInfoEventManager), manager);
            }
            return manager;
        }
    }

    public static void AddListener(object source, IWeakEventListener listener)
    {
        CurrentManager.ProtectedAddListener(source, listener);
    }

    protected override void StartListening(object source)
    {
        (source as CarDealer).NewCarInfo += CarDealer_NewCarInfo;
    }

    void CarDealer_NewCarInfo(object sender, CarIInfoEventArgs e)
    {
       DeliverEvent(sender, e);
    }

    protected override void StopListening(object source)
    {
        (source as CarDealer).NewCarInfo -= CarDealer_NewCarInfo;
    }
}

對于發(fā)布程序類CarDealer,不需要做任何修改室埋,其實現(xiàn)代碼與前面相同办绝。

2. 事件偵聽器

偵聽器需要改為實現(xiàn)IWeekEventListener接口。這個接口定義了ReceiveWeekEvent()姚淆,觸發(fā)事件時孕蝉,從弱事件管理器中調(diào)用這個方法。在該方法的實現(xiàn)代碼中腌逢,應從觸發(fā)的事件中調(diào)用NewCarIsHere()方法降淮。

public class Consumer : IWeakEventListener
{
    private string name;

    public Consumer(string name)
    {
        this.name = name;
    }

    public void NewCarIsHere(object sender, CarIInfoEventArgs e)
    {
        Console.WriteLine("{0}: car {1} is new", name, e.Car);
    }

    public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
    {
        NewCarIsHere(sender, e as CarIInfoEventArgs);
        return true;
    }
}

在Main方法中,連接發(fā)布程序和偵聽器搏讶,該連接現(xiàn)在使用WeekCarInfoEventManager類的AddListener()和RemoveListener()靜態(tài)方法佳鳖。

static void Main(string[] args)
{
    var dealer = new CarDealer();

    var michael = new Consumer("Michael");
    WeakCarInfoEventManager.AddListener(dealer, michael);

    dealer.NewCar("Mercedes");

    var nick = new Consumer("Nick");
    WeakCarInfoEventManager.AddListener(dealer, nick);

    dealer.NewCar("Ferrari");

    WeakCarInfoEventManager.AddListener(dealer, michael);
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市媒惕,隨后出現(xiàn)的幾起案子系吩,更是在濱河造成了極大的恐慌,老刑警劉巖妒蔚,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件穿挨,死亡現(xiàn)場離奇詭異,居然都是意外死亡肴盏,警方通過查閱死者的電腦和手機科盛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來菜皂,“玉大人贞绵,你說我怎么就攤上這事』衅” “怎么了榨崩?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長常侣。 經(jīng)常有香客問我蜡饵,道長弹渔,這世上最難降的妖魔是什么胳施? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮肢专,結(jié)果婚禮上舞肆,老公的妹妹穿的比我還像新娘焦辅。我一直安慰自己,他們只是感情好椿胯,可當我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布筷登。 她就那樣靜靜地躺著,像睡著了一般哩盲。 火紅的嫁衣襯著肌膚如雪前方。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天廉油,我揣著相機與錄音惠险,去河邊找鬼。 笑死抒线,一個胖子當著我的面吹牛班巩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嘶炭,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼抱慌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了眨猎?” 一聲冷哼從身側(cè)響起抑进,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宵呛,沒想到半個月后单匣,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡宝穗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年户秤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逮矛。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡鸡号,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出须鼎,到底是詐尸還是另有隱情鲸伴,我是刑警寧澤,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布晋控,位于F島的核電站汞窗,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏赡译。R本人自食惡果不足惜仲吏,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧裹唆,春花似錦誓斥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至成畦,卻和暖如春距芬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背循帐。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工蔑穴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人惧浴。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓存和,卻偏偏與公主長得像,于是被迫代替她去往敵國和親衷旅。 傳聞我的和親對象是個殘疾皇子捐腿,可洞房花燭夜當晚...
    茶點故事閱讀 44,654評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 前言 人生苦多,快來 Kotlin 柿顶,快速學習Kotlin茄袖! 什么是Kotlin? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,201評論 9 118
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,098評論 1 32
  • 寫在開頭:本人打算開始寫一個Kotlin系列的教程嘁锯,一是使自己記憶和理解的更加深刻宪祥,二是可以分享給同樣想學習Kot...
    胡奚冰閱讀 1,245評論 0 6
  • 從爬蟲必要的幾個基本需求來講: 1.抓取 py的urllib不一定去用,但是要學家乘,如果還沒用過的話蝗羊。 比較好的替代...
    Python程序媛閱讀 531評論 0 7
  • 夢里以為花間月 醒來原是滿地霜
    老去的牛糞閱讀 171評論 0 1