使用優(yōu)雅方式對(duì)參數(shù)驗(yàn)證進(jìn)行處理

我們?cè)谝话愕慕涌诤瘮?shù)開發(fā)中诱建,為了安全性蝴蜓,我們都需要對(duì)傳入的參數(shù)進(jìn)行驗(yàn)證,確保參數(shù)按照我們所希望的范圍輸入俺猿,如果在范圍之外茎匠,如空值,不符合的類型等等押袍,都應(yīng)該給出異乘忻埃或錯(cuò)誤提示信息。這個(gè)參數(shù)的驗(yàn)證處理有多種方式谊惭,最為簡(jiǎn)單的方式就是使用條件語句對(duì)參數(shù)進(jìn)行判斷汽馋,這樣的判斷代碼雖然容易理解侮东,但比較臃腫,如果對(duì)多個(gè)參數(shù)豹芯、多個(gè)條件進(jìn)行處理悄雅,那么代碼就非常臃腫難以維護(hù)了,本篇隨筆通過分析幾種不同的參數(shù)驗(yàn)證方式铁蹈,最終采用較為優(yōu)雅的方式進(jìn)行處理宽闲。
通常會(huì)規(guī)定類型參數(shù)是否允許為空,如果是字符可能有長(zhǎng)度限制握牧,如果是整數(shù)可能需要判斷范圍容诬,如果是一些特殊的類型比如電話號(hào)碼,郵件地址等沿腰,可能需要使用正則表達(dá)式進(jìn)行判斷览徒。參考隨筆《C# 中參數(shù)驗(yàn)證方式的演變》中文章的介紹,我們對(duì)參數(shù)的驗(yàn)證方式有幾種颂龙。

1吱殉、常規(guī)方式的參數(shù)驗(yàn)證

一般我們就是對(duì)方法的參數(shù)使用條件語句的方式進(jìn)行判斷,如下函數(shù)所示厘托。

public bool Register(string name, int age)
{
    if (string.IsNullOrEmpty(name))
    {
        throw new ArgumentException("name should not be empty", "name");
    }
    if (age < 10 || age > 70)
    {
        throw new ArgumentException("the age must between 10 and 70","age");
    }

    //insert into db
}

或者

public void Initialize(string name, int id)
{
    if (string.IsNullOrEmpty(value))
        throw new ArgumentException("name");
    if (id < 0) 
        throw new ArgumentOutOfRangeException("id");
    // Do some work here.
}

如果復(fù)雜的參數(shù)校驗(yàn)友雳,那么代碼就比較臃腫

void TheOldFashionWay(int id, IEnumerable<int> col, 
    DayOfWeek day)
{
    if (id < 1)
    {
        throw new ArgumentOutOfRangeException("id", 
            String.Format("id should be greater " +
            "than 0. The actual value is {0}.", id));
    }

    if (col == null)
    {
        throw new ArgumentNullException("col",
            "collection should not be empty");
    }

    if (col.Count() == 0)
    {
        throw new ArgumentException(
            "collection should not be empty", "col");
    }

    if (day >= DayOfWeek.Monday &&
        day <= DayOfWeek.Friday)
    {
        throw new InvalidEnumArgumentException(
            String.Format("day should be between " +
            "Monday and Friday. The actual value " +
            "is {0}.", day));
    }

    // Do method work
}

有時(shí)候?yàn)榱朔奖悖瑫?huì)把參數(shù)校驗(yàn)的方法铅匹,做一個(gè)通用的輔助類進(jìn)行處理押赊,如在我的公用類庫里面提供了一個(gè):參數(shù)驗(yàn)證的通用校驗(yàn)輔助類 ArgumentValidation,使用如下代碼所示包斑。

     public class TranContext:IDisposable   
     {   
         private readonly TranSetting setting=null;   
         private IBuilder builder=null;   
         private ILog log=null;   
         private ManuSetting section=null;   
         public event EndReportEventHandler EndReport;   
         public TranContext()   
         {   
        }   
        public TranContext(TranSetting setting)   
        {   
            ArgumentValidation.CheckForNullReference (setting,"TranSetting");   
            this.setting =setting;   
        }   
        public TranContext(string key,string askFileName,string operation)   
        {   
            ArgumentValidation.CheckForEmptyString (key,"key");   
            ArgumentValidation.CheckForEmptyString (askFileName,"askFileName");   
            ArgumentValidation.CheckForEmptyString (operation,"operation");   
            setting=new TranSetting (this,key,askFileName,operation);   
        }  

但是這樣的方式還是不夠完美流礁,不夠流暢。

2罗丰、基于第三方類庫的驗(yàn)證方式

在GitHub上有一些驗(yàn)證類庫也提供了對(duì)參數(shù)驗(yàn)證的功能神帅,使用起來比較簡(jiǎn)便,采用一種流暢的串聯(lián)寫法萌抵。如CuttingEdge.Conditions等找御。CuttingEdge.Condition 里面的例子代碼我們來看看。

public ICollection GetData(Nullable<int> id, string xml, IEnumerable<int> col)
{
    // Check all preconditions:
    Condition.Requires(id, "id")
        .IsNotNull()          // throws ArgumentNullException on failure
        .IsInRange(1, 999)    // ArgumentOutOfRangeException on failure
        .IsNotEqualTo(128);   // throws ArgumentException on failure

    Condition.Requires(xml, "xml")
        .StartsWith("<data>") // throws ArgumentException on failure
        .EndsWith("</data>") // throws ArgumentException on failure
        .Evaluate(xml.Contains("abc") || xml.Contains("cba")); // arg ex

    Condition.Requires(col, "col")
        .IsNotNull()          // throws ArgumentNullException on failure
        .IsEmpty()            // throws ArgumentException on failure
        .Evaluate(c => c.Contains(id.Value) || c.Contains(0)); // arg ex

    // Do some work

    // Example: Call a method that should not return null
    object result = BuildResults(xml, col);

    // Check all postconditions:
    Condition.Ensures(result, "result")
        .IsOfType(typeof(ICollection)); // throws PostconditionException on failure

    return (ICollection)result;
}
    
public static int[] Multiply(int[] left, int[] right)
{
    Condition.Requires(left, "left").IsNotNull();
    
    // You can add an optional description to each check
    Condition.Requires(right, "right")
        .IsNotNull()
        .HasLength(left.Length, "left and right should have the same length");
    
    // Do multiplication
}

這種書寫方式比較流暢绍填,而且也提供了比較強(qiáng)大的參數(shù)校驗(yàn)方式霎桅,除了可以使用其IsNotNull、IsEmpty等內(nèi)置函數(shù)讨永,也可以使用Evaluate這個(gè)擴(kuò)展判斷非常好的函數(shù)來處理一些自定義的判斷滔驶,應(yīng)該說可以滿足絕大多數(shù)的參數(shù)驗(yàn)證要求了,唯一不好的就是需要使用這個(gè)第三方類庫吧卿闹,有時(shí)候如需擴(kuò)展就麻煩一些揭糕。而且一般來說我們自己有一些公用類庫萝快,如果對(duì)參數(shù)驗(yàn)證也還需要引入一個(gè)類庫,還是比較麻煩一些的(個(gè)人見解)

3著角、Code Contract

Code Contracts 是微軟研究院開發(fā)的一個(gè)編程類庫杠巡,我最早看到是在C# In Depth 的第二版中,當(dāng)時(shí).NET 4.0還沒有出來雇寇,當(dāng)時(shí)是作為一個(gè)第三方類庫存在的氢拥,到了.NET 4.0之后,已經(jīng)加入到了.NET BCL中锨侯,該類存在于System.Diagnostics.Contracts 這個(gè)命名空間中嫩海。
這個(gè)是美其名曰:契約編程
C#代碼契約起源于微軟開發(fā)的一門研究語言Spec#(參見http://mng.bz/4147)。
    ? 契約工具:包括:ccrewrite(二進(jìn)制重寫器囚痴,基于項(xiàng)目的設(shè)置確保契約得以貫徹執(zhí)行)叁怪、ccrefgen(它生成契約引用集,為客戶端提供契約信息)深滚、cccheck(靜態(tài)檢查器奕谭,確保代碼能在編譯時(shí)滿足要求,而不是僅僅檢查在執(zhí)行時(shí)實(shí)際會(huì)發(fā)生什么)痴荐、ccdocgen(它可以為代碼中指定的契約生成xml文檔)血柳。
    ? 契約種類:前置條件、后置條件生兆、固定條件难捌、斷言和假設(shè)、舊式契約鸦难。
    ? 代碼契約工具下載及安裝:下載地址Http://mng.bz/cn2k根吁。(代碼契約工具并不包含在Visual Studio 2010中,但是其核心類型位于mscorlib內(nèi)合蔽。)
    ? 命名空間:System.Diagnostics.Contracts.Contract
Code Contract 使得.NET 中契約式設(shè)計(jì)和編程變得更加容易击敌,Contract中的這些靜態(tài)方法方法包括
Requires:函數(shù)入口處必須滿足的條件
Ensures:函數(shù)出口處必須滿足的條件
Invariants:所有成員函數(shù)出口處都必須滿足的條件
Assertions:在某一點(diǎn)必須滿足的條件
Assumptions:在某一點(diǎn)必然滿足的條件,用來減少不必要的警告信息

Code Contract 的使用文檔您可以從官網(wǎng)下載到拴事。為了方便使用Visual Studio開發(fā)沃斤。我們可以安裝一個(gè)Code Contracts for .NET 插件。安裝完了之后挤聘,點(diǎn)擊Visual Studio中的項(xiàng)目屬性轰枝,可以看到如下豐富的選擇項(xiàng):


Contract和Debug.Assert有些地方相似:
都提供了運(yùn)行時(shí)支持:這些Contracts都是可以被運(yùn)行的,并且一旦條件不被滿足组去,會(huì)彈出類似Assert的一樣的對(duì)話框報(bào)錯(cuò),如下:
都可以在隨意的在代碼中關(guān)閉打開步淹。

但是Contract有更多和更強(qiáng)大的功能:
Contracts的意圖更加清晰从隆,通過不同的Requires/Ensures等等調(diào)用诚撵,代表不同類型的條件,比單純的Assert更容易理解和進(jìn)行自動(dòng)分析
Contracts的位置更加統(tǒng)一键闺,將3種不同條件都放在代碼的開始處寿烟,而非散見在函數(shù)的開頭和結(jié)尾,便于查找和分析辛燥。
不同的開發(fā)人員筛武、不同的小組、不同的公司挎塌、不同的庫可能都會(huì)有自己的Assert徘六,這就大大增加了自動(dòng)分析的難度,也不利于開發(fā)人員編寫代碼榴都。而Contracts直接被.NET 4.0支持待锈,是統(tǒng)一的。
它提供了靜態(tài)分析支持嘴高,這個(gè)我們可以通過配置面板看到竿音,通過靜態(tài)分析Contracts,靜態(tài)分析工具可以比較容易掌握函數(shù)的各種有關(guān)信息拴驮,甚至可以作為Intellisense

Contract中包含了三個(gè)工具:
ccrewrite, 通過向程序集中些如二進(jìn)制數(shù)據(jù)春瞬,來支持運(yùn)行時(shí)檢測(cè)
cccheck, 運(yùn)行時(shí)檢測(cè)
ccdoc, 將Contract自動(dòng)生成XML文檔

前置條件的處理,如代碼所示套啤。

       /// <summary>
        /// 實(shí)現(xiàn)“前置條件”的代碼契約
        /// </summary>
        /// <param name="text">Input</param>
        /// <returns>Output</returns>
        public static int CountWhiteSpace(string text)
        {
            // 命名空間:using System.Diagnostics.Contracts;
            Contract.Requires<ArgumentNullException>(text != null, "Paramter:text");// 使用了泛型形式的Requires
            return text.Count(char.IsWhiteSpace);
        }

后置條件(postcondition):表示對(duì)方法輸出的約束:返回值快鱼、out或ref參數(shù)的值,以及任何被改變的狀態(tài)纲岭。Ensures();

        /// <summary>
        /// 實(shí)現(xiàn)“后置條件”的代碼契約
        /// </summary>
        /// <param name="text">Input</param>
        /// <returns>Output</returns>
        public static int CountWhiteSpace(string text)
        {
            // 命名空間:using System.Diagnostics.Contracts;
            Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(text), "text"); // 使用了泛型形式的Requires
            Contract.Ensures(Contract.Result<int>() > 0); // 1.方法在return之前抹竹,所有的契約都要在真正執(zhí)行方法之前(Assert和Assume除外,下面會(huì)介紹)止潮。
                                                          // 2.實(shí)際上Result<int>()僅僅是編譯器知道的”占位符“:在使用的時(shí)候工具知道它代表了”我們將得到那個(gè)返回值“窃判。
            return text.Count(char.IsWhiteSpace);
        }

        public static bool TryParsePreserveValue(string text, ref int value)
        {
            Contract.Ensures(Contract.Result<bool>() || Contract.OldValue(value) == Contract.ValueAtReturn(out value)); // 此結(jié)果表達(dá)式是無法證明真?zhèn)蔚摹?            return int.TryParse(text, out value); // 所以此處在編譯前就會(huì)提示錯(cuò)誤信息:Code Contract:ensures unproven: XXXXX
        }

這個(gè)代碼契約功能比較強(qiáng)大,不過好像對(duì)于簡(jiǎn)單的參數(shù)校驗(yàn)喇闸,引入這么一個(gè)家伙感覺麻煩袄琳,也不見開發(fā)人員用的有多廣泛,而且還需要提前安裝一個(gè)工具:Code Contracts for .NET燃乍。
因此我也不傾向于使用這個(gè)插件的東西唆樊,因?yàn)榇a要交付客戶使用,要求客戶安裝一個(gè)插件刻蟹,并且打開相關(guān)的代碼契約設(shè)置逗旁,還是比較麻煩,如果沒有打開舆瘪,也不會(huì)告訴客戶代碼編譯出錯(cuò)片效,只是會(huì)在運(yùn)行的時(shí)候不校驗(yàn)方法參數(shù)红伦。

4、使用內(nèi)置的公用類庫處理

基于CuttingEdge.Conditions 的方式淀衣,其實(shí)我們也可以做一個(gè)類似這樣的流暢性寫法的校驗(yàn)處理昙读,而且不需要那么麻煩引入第三方類庫。
例如我們?cè)诠妙悗炖锩嬖黾右粋€(gè)類庫膨桥,如下代碼所示蛮浑。

    /// <summary>
    /// 參數(shù)驗(yàn)證幫助類,使用擴(kuò)展函數(shù)實(shí)現(xiàn)
    /// </summary>
    /// <example>
    /// eg:
    /// ArgumentCheck.Begin().NotNull(sourceArray, "需要操作的數(shù)組").NotNull(addArray, "被添加的數(shù)組");
    /// </example>
    public static class ArgumentCheck
    {
        #region Methods
        
        /// <summary>
        /// 驗(yàn)證初始化
        /// <para>
        /// eg:
        /// ArgumentCheck.Begin().NotNull(sourceArray, "需要操作的數(shù)組").NotNull(addArray, "被添加的數(shù)組");
        /// </para>
        /// <para>
        /// ArgumentCheck.Begin().NotNullOrEmpty(tableName, "表名").NotNullOrEmpty(primaryKey, "主鍵");</para>
        /// <para>
        /// ArgumentCheck.Begin().CheckLessThan(percent, "百分比", 100, true);</para>
        /// <para>
        /// ArgumentCheck.Begin().CheckGreaterThan&lt;int&gt;(pageIndex, "頁索引", 0, false).CheckGreaterThan&lt;int&gt;(pageSize, "頁大小", 0, false);</para>
        /// <para>
        /// ArgumentCheck.Begin().NotNullOrEmpty(filepath, "文件路徑").IsFilePath(filepath).NotNullOrEmpty(regexString, "正則表達(dá)式");</para>
        /// <para>
        /// ArgumentCheck.Begin().NotNullOrEmpty(libFilePath, "非托管DLL路徑").IsFilePath(libFilePath).CheckFileExists(libFilePath);</para>
        /// <para>
        /// ArgumentCheck.Begin().InRange(brightnessValue, 0, 100, "圖片亮度值");</para>
        /// <para>
        /// ArgumentCheck.Begin().Check&lt;ArgumentNullException&gt;(() => config.HasFile, "config文件不存在只嚣。");</para>
        /// <para>
        /// ArgumentCheck.Begin().NotNull(serialPort, "串口").Check&lt;ArgumentException&gt;(() => serialPort.IsOpen, "串口尚未打開沮稚!").NotNull(data, "串口發(fā)送數(shù)據(jù)");
        /// </para>
        /// </summary>
        /// <returns>Validation對(duì)象</returns>
        public static Validation Begin()
        {
            return null;
        }
        
        /// <summary>
        /// 需要驗(yàn)證的正則表達(dá)式
        /// </summary>
        /// <param name="validation">Validation</param>
        /// <param name="checkFactory">委托</param>
        /// <param name="argumentName">參數(shù)名稱</param>
        /// <returns>Validation對(duì)象</returns>
        public static Validation Check(this Validation validation, Func<bool> checkFactory, string argumentName)
        {
            return Check<ArgumentException>(validation, checkFactory, string.Format(Resource.ParameterCheck_Match2, argumentName));
        }
        
        /// <summary>
        /// 自定義參數(shù)檢查
        /// </summary>
        /// <typeparam name="TException">泛型</typeparam>
        /// <param name="validation">Validation</param>
        /// <param name="checkedFactory">委托</param>
        /// <param name="message">自定義錯(cuò)誤消息</param>
        /// <returns>Validation對(duì)象</returns>
        public static Validation Check<TException>(this Validation validation, Func<bool> checkedFactory, string message)
        where TException : Exception
        {
            if(checkedFactory())
            {
                return validation ?? new Validation()
                {
                    IsValid = true
                };
            }
            else
            {
                TException _exception = (TException)Activator.CreateInstance(typeof(TException), message);
                throw _exception;
            }
        }
......

上面提供了一個(gè)常規(guī)的檢查和泛型類型檢查的通用方法,我們?nèi)绻枰獙?duì)參數(shù)檢查介牙,如下代碼所示壮虫。

ArgumentCheck.Begin().NotNull(sourceArray, "需要操作的數(shù)組").NotNull(addArray, "被添加的數(shù)組");

而這個(gè)NotNull就是我們根據(jù)上面的定義方法進(jìn)行擴(kuò)展的函數(shù),如下代碼所示环础。

        /// <summary>
        /// 驗(yàn)證非空
        /// </summary>
        /// <param name="validation">Validation</param>
        /// <param name="data">輸入項(xiàng)</param>
        /// <param name="argumentName">參數(shù)名稱</param>
        /// <returns>Validation對(duì)象</returns>
        public static Validation NotNull(this Validation validation, object data, string argumentName)
        {
            return Check<ArgumentNullException>(validation, () => (data != null), string.Format(Resource.ParameterCheck_NotNull, argumentName));
        }

同樣道理我們可以擴(kuò)展更多的自定義檢查方法囚似,如引入正則表達(dá)式的處理。

ArgumentCheck.Begin().NotNullOrEmpty(libFilePath, "非托管DLL路徑").IsFilePath(libFilePath).CheckFileExists(libFilePath);

它的擴(kuò)展函數(shù)如下所示线得。

        /// <summary>
        /// 是否是文件路徑
        /// </summary>
        /// <param name="validation">Validation</param>
        /// <param name="data">路徑</param>
        /// <returns>Validation對(duì)象</returns>
        public static Validation IsFilePath(this Validation validation, string data)
        {
            return Check<ArgumentException>(validation, () => ValidateUtil.IsFilePath(data), string.Format(Resource.ParameterCheck_IsFilePath, data));
        }

        /// <summary>
        /// 檢查指定路徑的文件必須存在饶唤,否則拋出<see cref="FileNotFoundException"/>異常。
        /// </summary>
        /// <param name="validation">Validation</param>
        /// <param name="filePath">文件路徑</param>
        /// <exception cref="ArgumentNullException">當(dāng)文件路徑為null時(shí)</exception>
        /// <exception cref="FileNotFoundException">當(dāng)文件路徑不存在時(shí)</exception>
        /// <returns>Validation對(duì)象</returns>
        public static Validation CheckFileExists(this Validation validation, string filePath)
        {
            return Check<FileNotFoundException>(validation, () => File.Exists(filePath), string.Format(Resource.ParameterCheck_FileNotExists, filePath));
        }

我們可以根據(jù)我們的正則表達(dá)式校驗(yàn)贯钩,封裝更多的函數(shù)進(jìn)行快速使用募狂,如果要自定義的校驗(yàn),那么就使用基礎(chǔ)的Chek函數(shù)即可角雷。



測(cè)試下代碼使用祸穷,如下所示。

       /// <summary>
        /// 應(yīng)用程序的主入口點(diǎn)勺三。
        /// </summary>
        [STAThread]
        static void Main(string[] args)
        {
            ArgumentCheck.Begin().NotNull(args, "啟動(dòng)參數(shù)");
            string test = null;
            ArgumentCheck.Begin().NotNull(test, "測(cè)試參數(shù)").NotEqual(test, "abc", "test");

這個(gè)ArgumentCheck作為公用類庫的一個(gè)類雷滚,因此使用起來不需要再次引入第三方類庫,也能夠?qū)崿F(xiàn)常規(guī)的校驗(yàn)處理吗坚,以及可以擴(kuò)展自定義的參數(shù)校驗(yàn)祈远,同時(shí)也是支持流式的書寫方式,非常方便商源。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末车份,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子牡彻,更是在濱河造成了極大的恐慌扫沼,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異充甚,居然都是意外死亡以政,警方通過查閱死者的電腦和手機(jī)霸褒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門伴找,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人废菱,你說我怎么就攤上這事技矮。” “怎么了殊轴?”我有些...
    開封第一講書人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵衰倦,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我旁理,道長(zhǎng)樊零,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任孽文,我火速辦了婚禮驻襟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘芋哭。我一直安慰自己沉衣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開白布减牺。 她就那樣靜靜地躺著豌习,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拔疚。 梳的紋絲不亂的頭發(fā)上肥隆,一...
    開封第一講書人閱讀 52,337評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音稚失,去河邊找鬼栋艳。 笑死,一個(gè)胖子當(dāng)著我的面吹牛墩虹,可吹牛的內(nèi)容都是我干的嘱巾。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼诫钓,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼旬昭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起菌湃,我...
    開封第一講書人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤问拘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體骤坐,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡绪杏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了纽绍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蕾久。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖拌夏,靈堂內(nèi)的尸體忽然破棺而出僧著,到底是詐尸還是另有隱情,我是刑警寧澤障簿,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布盹愚,位于F島的核電站,受9級(jí)特大地震影響站故,放射性物質(zhì)發(fā)生泄漏皆怕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一西篓、第九天 我趴在偏房一處隱蔽的房頂上張望愈腾。 院中可真熱鬧,春花似錦污淋、人聲如沸顶滩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽礁鲁。三九已至,卻和暖如春赁豆,著一層夾襖步出監(jiān)牢的瞬間仅醇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工魔种, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留析二,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓节预,卻偏偏與公主長(zhǎng)得像叶摄,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子安拟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359

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