C#3.0新特性——委托详解

前言

 

委托的定义

委托的本质:函数指针。让方法作为变量一样传递。

定义:委托是一种类型安全的函数回调机制, 它不仅能够调用实例方法,也能调用静态方法,并且具备按顺序执行多个方法的能力。

也就是说,委托可以在程序运行时调用不同方法函数,只要这个方法签名和委托签名保持一致。与函数指针不同的是,委托是类型安全的。所谓类型安全,是指在编译时编译器会检测委托对象的签名是否委托声明一致。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DelegateSamples
{
    //声明一个委托,参数为string,无返回值
    delegate void DelSamples(string msg);
    class Program
    {
        static void Main(string[] args)
        {
            DelSamples delSample = new Program().SpeakChinese; //调用实例方法
            delSample += SpeakEnglish; //调用静态方法
            delSample("KoalaStudio");
            Console.ReadKey();
        }

        private void SpeakChinese(string msg)
        {
            Console.WriteLine("你好,我是{0}",msg);
        }

        private static void SpeakEnglish(string msg)
        {
            Console.WriteLine("Hello,I‘m {0}",msg);
        }
    }
}

委托的声明

简单委托

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DelegateSamples
{
    //声明一个委托,参数为string,无返回值
    delegate void DelSamples(string msg);
    class Program
    {
        static void Main(string[] args)
        {
          //使用new关键字
          DelSamples delSample = new DelSamples(new Program().SpeakChinese);
          delSample("Koala工作室");

          //不使用new,自动推断委托类型
          DelSamples delSample2 = SpeakEnglish;
          delSample2("KoalaStudio");

          //利用Lambda表达式
          DelSamples delSample3 = (string msg) => SpeakEnglish(msg);
          delSample3("KoalaStudio");

          Console.ReadKey();
        }

        private void SpeakChinese(string msg)
        {
            Console.WriteLine("你好,我是{0}",msg);
        }

        private static void SpeakEnglish(string msg)
        {
            Console.WriteLine("Hello,I‘m {0}",msg);
        }
    }
}

匿名委托

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DelegateSamples
{
    //声明一个委托,参数为string,无返回值
    delegate void DelSamples(string msg);
    class Program
    {
        static void Main(string[] args)
        {
          //匿名委托
          DelSamples delSample4 = delegate(string msg)
          {
              Console.WriteLine("你好,我是{0}", msg);
          };
          delSample4("KoalaStudio");

          //利用Lambda表达式实现匿名委托
          DelSamples delSample5 = (string msg) => {
             Console.WriteLine("你好,我是{0}", msg);
          };
          delSample5("KoalaStudio");

          Console.ReadKey();
        }

        private void SpeakChinese(string msg)
        {
            Console.WriteLine("你好,我是{0}",msg);
        }

        private static void SpeakEnglish(string msg)
        {
            Console.WriteLine("Hello,I‘m {0}",msg);
        }
    }
}

匿名委托的写法更加优雅,但是需要注意两点:1、在函数内部不能使用跳转语句跳出函数外部;2、不能使用ref和out等关键字

多播委托

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DelegateSamples
{
    //声明一个委托,参数为string,无返回值
    delegate void DelSamples(string msg);
    class Program
    {
        static void Main(string[] args)
        {
            //多播委托可以带返回值,但是只有最后一个方法的返回值会被返回。
            DelSamples delSample6 = new Program().SpeakChinese;
            delSample6 += SpeakEnglish;
            delSample6("KoalaStudio");

            Console.ReadKey();
        }

        private void SpeakChinese(string msg)
        {
            Console.WriteLine("你好,我是{0}",msg);
        }

        private static void SpeakEnglish(string msg)
        {
            Console.WriteLine("Hello,I‘m {0}",msg);
        }
    }
}

多播委托可以连续执行函数,但是如果函数有返回值,那只有最后一个函数的返回值会被正确返回.

泛型委托

Action<T>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DelegateSamples
{
    class Program
    {
        static void Main(string[] args)
        {
            /* Action<T>:封装只有一个参数(类型为T),不包括返回值的签名函数,它包括以下几种情况:
             * Action<T>、Action<T1,T2>、Action<T1,T2,T3>、Action<T1,T2,T3,T4>
             * 声明:
             * delegate void Action();
             * delegate void Action<T1>(T1 arg1);
             * delegate void Action<T1,T2>(T1 arg1,T2 arg2);
             * delegate void Action<T1,T2,T3>(T1 arg1,T2 arg2,T3 arg3);
             * delegate void Action<T1,T2,T3,T4>(T1 arg1,T2 arg2,T3 arg3,T4 arg4);
             */
            Action<string> action = SpeakEnglish;
            action("KoalaStudio");

            Action<string, string> action2 = SpeakTwoLanguage;
            action2("KoalaStudio","Koala工作室");
            Console.ReadKey();
        }

        private void SpeakChinese(string msg)
        {
            Console.WriteLine("你好,我是{0}",msg);
        }

        private static void SpeakEnglish(string msg)
        {
            Console.WriteLine("Hello,I‘m {0}",msg);
        }

        private static void SpeakTwoLanguage(string msg1, string msg2)
        {
            Console.WriteLine("你好,我是{0}",msg1);
            Console.WriteLine("Hello,I‘m {0}",msg2);
        }
    }
}

Func<T,TResult>:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DelegateSamples
{
    class Program
    {
        static void Main(string[] args)
        {
            /* Func<T,TResult>:封装一个具有参数(类型为T),返回TResult类型值的签名函数,它包括以下几种情况:
             * Func<T,TResult>、Func<T1,T2,TResult>、Func<T1,T2,T3,TResult>、Func<T1,T2,T3,T4,TResult>
             * 声明:
             * ……略去
             */
            Func<string,string/*这是返回值类型*/> func = SpeakEnglish;
            func("KoalaStudio");

            Func<string, string, string/*这是返回值类型*/> func2 = SpeakTwoLanguage;
            func2("KoalaStudio","Koala工作室");
            Console.ReadKey();
        }

        private static string SpeakEnglish(string msg)
        {
            return string.Format("Hello,I‘m {0}", msg);
        }

        private static string SpeakTwoLanguage(string msg1, string msg2)
        {
            Console.WriteLine("你好,我是{0}",msg1);
            Console.WriteLine("Hello,I‘m {0}",msg2);
            return string.Format("你好,我是{0};Hello,I‘m {1}", msg1,msg2);
        }
    }
}

Predicate<T>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DelegateSamples
{
    class Program
    {
        static void Main(string[] args)
        {
            /* bool Predicate<T>:表示定义一组条件并确定指定对象是否符合这些条件的方法。
             * 通常,此类委托由Array和List类的几种方法使用,用于在集合中搜索元素。
             * delegate bool Predicate<T>(T obj),如果obj符合此委托表示的方法中定义的条件,则返回true,否则返回false
             */

            List<string> listString = new List<string>()
            {
                "a","abc","koala","xyz","take"
            };

            //List对象的FindAll定义:public List<T> FindAll(Predicate<T> match);
            //match 类型:System.Predicate<T> 委托,用于定义要搜索的元素应满足的条件。
            //返回值
            //类型:System.Collections.Generic.List<T>
            //如果找到,则为一个 List<T>,其中包含与指定谓词所定义的条件相匹配的所有元素;否则为一个空 List<T>。
            Predicate<String> match = delegate(string word)
            {
               if (word.Length > 4)
               {
                   return true;
               }
               return false;
            };

            List<string> result = listString.FindAll(match);
        }
    }
}

看到这里相信大家都已经感觉出来了,委托的申明和使用和类非常相似。没错,委托本质上就是一个类。实际上,我们用delegate关键字声明的所有委托都继承自System.MulticastDelegate类,这个类又继承自System.Delegate类,而System.Delegate类则继承自System.Object。尽管如此,我们并不能直接声明一个继承自System.MulticastDelegate类的委托,委托必须用delegate关键字声明。在我们声明委托时,编译器为我们完成了很多复杂的工作,有兴趣的朋友可以查阅相关资料。不过,即使不清楚编译器为我们干了什么也没有关系,只要知道在我们调用委托时,编译器自动为委托创建了BeginInvoke、EndInvoke和Invoke三个方法。BeginInvoke和EndInvoke方法用来实现异步委托调用,后面我们再详细介绍。

DelSamples delSample2 = SpeakEnglish;
delSample2("KoalaStudio");
//其实是调用编译器生成的Invoke方法
delSample2.Invoke("KoalaStudio");

写到这儿,其实已经把委托的基本概念介绍完了。接下去的内容会稍微复杂一些,刚刚接触委托的朋友可以跳过这部分,直接阅读委托与事件部分。

 

协变与逆变

  MSDN的解释:从Visual Studio2008开始.NET引入了变体支持,用于委托中匹配方法签名和委托类型。这意味着,我们不仅可以为委托指派具有匹配签名的方法,而且可以指派这样的方法:它们返回与委托类型指定的派生类型相比,派生程度更大的类型(协变),或者接受相比之下,派生程度更小的类型的参数(逆变)。相当拗口,对不对?不知道它想说明什么。稍微休息一下,忘记之前那长串的描述。我们来想一个情形:面向对象的一个典型应用就是继承和多态。假如我们定义了一个委托,它返回的类型是一个基类对象。如果有个方法,它的参数签名符合我们定义的委托参数签名,但是返回的是继承该基类的子类对象,那么这个方法是否能够使用该委托?又比如,我们定义了一个委托,它具有指定的参数签名。如果有个方法,它的参数是委托签名中的方法参数的基类(委托方法签名中的参数派生自调用方法的参数),那么这个方法是否能够使用该委托?答案是可以的!这也就是MSDN想说的:协变就是委托的类型返回值是它所指向函数(即调用的方法)的返回值的基类;逆变就是委托的类型参数是它所指向函数的参数的派生类。协变允许方法具有的派生返回类型比委托中定义的更多。 逆变允许方法具有的派生参数类型比委托类型中的更少。MSDN上提供的一个协变的例子:

class Mammals{}
class Dogs : Mammals{}

class Program
{
    // 定义一个委托,返回基类.
    public delegate Mammals HandlerMethod();

    public static Mammals MammalsHandler()
    {
        return null;
    }

    public static Dogs DogsHandler()
    {
        return null;
    }

    static void Test()
    {
        HandlerMethod handlerMammals = MammalsHandler;

        // 被允许.
        HandlerMethod handlerDogs = DogsHandler;
    }
}

再看个逆变的例子:

// Event hander接受一个EventArgs类型的参数.
private void MultiHandler(object sender, System.EventArgs e)
{
    label1.Text = System.DateTime.Now.ToString();
}

public Form1()
{
    InitializeComponent();

    // KeyDown期望接受的是KeyEventArgs对象,但是我们给的是EventArgs对象
    this.button1.KeyDown += this.MultiHandler;

    this.button1.MouseClick += this.MultiHandler;

}

协变和逆变先介绍基本的应用,其它还包括泛型委托的协变和逆变,有兴趣的朋友可以参考MSDN里的例子,这里不再赘述。

异步委托

  既然委托可以在运行时调用方法函数,如果调用的方法非常复杂耗时,主线程是不是会被阻塞以等待方法执行?我们看一段代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace DelegateSamples
{
    public delegate void delSample ();
    class Program
    {
        static void Main(string[] args)
        {
            delSample _sample = DoTask;
            _sample();
            Console.WriteLine("执行另一项工作。");
            Console.ReadKey();
        }

        public static void DoTask()
        {
            Console.WriteLine("开始执行复杂工作。");
            //线程阻塞5s,模拟进行复杂的工作。
            Thread.Sleep(TimeSpan.FromSeconds(5));
            Console.WriteLine("工作执行完毕。");
        }
    }
}

从下面的输出结果我们看到,委托调用的是主线程,因此复杂的方法函数会导致主线程阻塞,影响用户体验。那有没有办法可以改变呢?有!异步委托。

异步委托会从线程池中开辟一个新的线程用来调用方法,我们改动一下上面的代码,开启异步委托:

namespace DelegateSamples
{
    public delegate void delSample ();
    class Program
    {
        static void Main(string[] args)
        {
            delSample _sample = DoTask;
            _sample.BeginInvoke(null, null);
            Console.WriteLine("执行另一项工作。");
            Console.ReadKey();
        }

        public static void DoTask()
        {
            Console.WriteLine("开始执行复杂工作。");
            //线程阻塞5s,模拟进行复杂的工作。
            Thread.Sleep(TimeSpan.FromSeconds(5));
            Console.WriteLine("工作执行完毕。");
        }
    }
}

看一下返回结果:

    好像和我们预想的不太一样,主线程并没有等待委托的执行,而是继续执行下面的操作并退出。这是什么原因?还是和线程有关。默认线程池中的线程都是后台线程,主线程不会等待后台线程的执行,仿佛它根本不存在。那有办法解决这个问题吗?能不能让主线程等待委托线程执行完毕在退出?我们再看一下MSDN给出的解释:BeingInvoke方法启动异步委托,它具有和异步执行的方法相同的参数,同时还有两个可选参数。第一个参数是AsyncCallBack委托,该委托引用在异步调用完成时要调用的方法(即回调函数);第二个参数是一个用户定义的对象,可以将信息传入回调函数。BeginInvoke立即返回而不等待异步调用完成(这就解释了上面的原因),但是会返回一个用于监控异步调用进度的IAsyncResult。EndInvoke方法检索异步调用的结果。在调用 BeginInvoke 之后随时可以调用该方法。如果异步调用尚未完成,则 EndInvoke 会一直阻止调用线程,直到异步调用完成。我们改一下上面的代码:

namespace DelegateSamples
{
    public delegate void delSample ();
    class Program
    {
        static void Main(string[] args)
        {
            delSample _sample = DoTask;
            IAsyncResult result = _sample.BeginInvoke(null, null);
            while (result.IsCompleted)
            {
                //可以监控异步方法执行是否完成
                //do task
            }
            _sample.EndInvoke(result);
            Console.WriteLine("执行另一项工作。");
            Console.ReadKey();
        }

        public static void DoTask()
        {
            Console.WriteLine("开始执行复杂工作。");
            //线程阻塞5s,模拟进行复杂的工作。
            Thread.Sleep(TimeSpan.FromSeconds(5));
            Console.WriteLine("工作执行完毕。");
        }
    }
}

从输出结果看,委托线程确实阻塞了主线程,但是这依然会影响用户体验。我们希望的流程是:主线程调用委托线程后继续执行后面的逻辑业务,等逻辑业务完成后等待委托线程的返回。我们利用WaitHandle等待异步调用来解决这个问题,异步调用完成时会发出WaitHandle信号,我们可以通过调用WaitOne方法等待该信号。

namespace DelegateSamples
{
    public delegate void delSample ();
    class Program
    {
        static void Main(string[] args)
        {
            delSample _sample = DoTask;
            IAsyncResult result = _sample.BeginInvoke(null, null);
            //等待1s,执行主线程
            result.AsyncWaitHandle.WaitOne(1,false);
//_sample();
            //_sample.EndInvoke(result);
            Console.WriteLine("执行另一项工作。");
            _sample.EndInvoke(result);
            Console.ReadKey();
        }

        public static void DoTask()
        {
            Console.WriteLine("开始执行复杂工作。");
            //线程阻塞5s,模拟进行复杂的工作。
              Thread.Sleep(TimeSpan.FromSeconds(5));
            Console.WriteLine("工作执行完毕。");
        }
    }
}

再看一种回调模式

namespace DelegateSamples
{
    public delegate void delSample ();
    class Program
    {
        static void Main(string[] args)
        {
            delSample _sample = DoTask;
            _sample.BeginInvoke(
                delegate(IAsyncResult ar)
                { _sample.EndInvoke(ar); },null
            );
            Console.WriteLine("执行另一项工作。");
            Console.ReadKey();
        }

        public static void DoTask()
        {
            Console.WriteLine("开始执行复杂工作。");
            //线程阻塞5s,模拟进行复杂的工作。
            Thread.Sleep(TimeSpan.FromSeconds(5));
            Console.WriteLine("工作执行完毕。");
        }
    }
}

回调模式是在BeginInvoke后立刻调用回调函数,这个回调函数是在 ThreadPool线程上进行的,因此主线程将继续执行。ThreadPool线程是后台线程,这些线程不会在主线程结束后保持应用程序的运行,因此主线程必须休眠足够长的时间以便回调完成。我们也可以在主线程完成操作后调用IsCompleted属性判断委托函数是否完成。

委托与事件

既然说了委托,自然离不开事件。在这里我不打算详细阐述事件机制及其相关的内容,后面我会再整理一篇有关事件的文章。我打算从委托出发,看看事件是怎么和委托扯上关系的。先看一段代码:

namespace DelegateSamples
{
    public class Program
    {
        static void Main(string[] args)
        {
            Publish publish = new Publish();
            Subscribe subscribe = new Subscribe();
            publish.delEventHandler += subscribe.OnLeaveMessage;
            publish.OnFire("我触发了委托!");
            //调用委托
            publish.delEventHandler("我触发了委托!");
            Console.ReadKey();
        }

    }
    //定义委托
    public delegate void FireEventHandler(string msg);
    public class Publish
    {
        public FireEventHandler delEventHandler;

        public void OnFire(string msg)
        {
            if (delEventHandler != null)
            {
                delEventHandler(msg);
            }
        }

    }

    public class Subscribe
    {
        public Subscribe(){}

        public void OnLeaveMessage(string msg)
        {
            Console.WriteLine(msg);
        }
    }
}

这里我们发现,如果要使用委托变量,那么在类的内部委托变量必须是public,这就显得不安全。我只希望外部通过Publish.OnFire()来执行委托,但是外部仍然可以直接调用委托。于是,我们换一个写法,再看代码:

namespace DelegateSamples
{
    public class Program
    {
        static void Main(string[] args)
        {
            Publish publish = new Publish();
            Subscribe subscribe = new Subscribe();
            publish.delEventHandler += subscribe.OnLeaveMessage;
            publish.OnFire("我触发了委托!");
            //调用委托
            publish.delEventHandler("我触发了委托!");
            Console.ReadKey();
        }

    }
    //定义委托
    public delegate void FireEventHandler(string msg);
    public class Publish
    {
        //public FireEventHandler delEventHandler;
        //不使用委托关键字delegate,采用event关键字
        public event FireEventHandler delEventHandler;
        public void OnFire(string msg)
        {
            if (delEventHandler != null)
            {
                delEventHandler(msg);
            }
        }

    }

    public class Subscribe
    {
        public Subscribe(){}

        public void OnLeaveMessage(string msg)
        {
            Console.WriteLine(msg);
        }
    }
}

publish.delEventHandler("我触发了委托!"),编译无法通过,这意味着外部不能直接调用委托变量,尽管他的属性还是public。因为加了event关键字,本质上是生成了私有的委托变量,减少了外部对类内部变量的修改权利。说到这里,我们再来看一下事件和委托的关系。在CLR综合那个事件模型是建立在委托机制上的,这就是他们之间的联系。委托是对方法的抽象,它将方法的调用与实现分离,方法的调用者(即委托的执行者)不知道方法内部的实现,而方法的实现者也不知道方法什么时候会被调用。正是因为委托具有这样的特性,才使得它理所当然的用来实现事件机制。因为事件被触发后执行什么操作,是由触发者决定的,而事件拥有者只知道什么情况下会触发事件,但不知道事件的具体实现。

    最后,我们看一下事件的定义。事件用event关键字定义,其类型是一个委托类型,这体现了事件是通过委托来实现的。上面的代码已经展示了最基本的事件定义方式,即:自定义一个委托,然后再申明一个event事件。在.NET3.5开始,由于支持泛型,定义事件时可以不需要再自定义委托,统一采用System.EventHandler<TEventArgs>委托,如何改造上面代码,请大家自行修改。

时间: 2024-11-07 17:30:57

C#3.0新特性——委托详解的相关文章

HTML5新特性及详解

什么是HTML5:HTML5 是下一代的HTML,将成为 HTML.XHTML 以及 HTML DOM 的新标准. 为 HTML5 建立的一些规则: 新特性应该基于 HTML.CSS.DOM 以及 JavaScript. 减少对外部插件的需求(比如 Flash) 更优秀的错误处理 更多取代脚本的标记 HTML5 应该独立于设备 开发进程应对公众透明 HTML5 中的一些有趣的新特性: 用于绘画的 canvas 元素 用于媒介回放的 video 和 audio 元素 对本地离线存储的更好的支持 新

Php5.5新特性 Generators详解

在**PHP5.5.0**版本中,新增了生成器*(Generators)*特性,用于简化实现迭代器接口*(Iterator)*创建简单的迭代器的复杂性. 通过生成器,我们可以轻松的使用foreach迭代一系列的数据,而不需要事先在内存中构建要被迭代的对象,大大减少了内存开销. 当生成器函数被调用的时候,它会返回一个可迭代的对象,当对该对象进行迭代的时候,PHP将会在需要的时候调用生成器函数,并且在生成器使用新增的关键字yield产生一个新的值的时候,保存迭代器内部的状态.迭代器没有新的值需要产生

有关Material Design新特性的详解。

源码地址:https://github.com/chrisbanes/cheesesquare 自己添加备注后的地址:https://github.com/heinika/DrawerLayoutDemo 英文教程:https://guides.codepath.com/android/Handling-Scrolls-with-CoordinatorLayout 中文教程:http://my.oschina.net/kooeasy/blog/484593 主要包含: DrawerLayout

Servlet 3.0 新特性详解

转自:https://www.ibm.com/developerworks/cn/java/j-lo-servlet30/ Servlet 3.0 新特性详解 张 建平2010 年 4 月 23 日发布 WeiboGoogle+用电子邮件发送本页面 6 Servlet 3.0 新特性概述 Servlet 3.0 作为 Java EE 6 规范体系中一员,随着 Java EE 6 规范一起发布.该版本在前一版本(Servlet 2.5)的基础上提供了若干新特性用于简化 Web 应用的开发和部署.其

[转]Servlet 3.0 新特性详解

原文地址:http://blog.csdn.net/xiazdong/article/details/7208316 Servlet 3.0 新特性概览 1.Servlet.Filter.Listener无需在web.xml中进行配置,可以通过Annotation进行配置: 2.模块化编程,即将各个Servlet模块化,将配置文件也分开配置. 3.Servlet异步处理,应对复杂业务处理: 4.异步Listener,对于异步处理的创建.完成等进行监听: 5. 文件上传API简化: tomcat

Servlet 3.0 新特性详解 (转载)

原文地址:https://www.ibm.com/developerworks/cn/java/j-lo-servlet30/ Servlet 3.0 新特性概述 Servlet 3.0 作为 Java EE 6 规范体系中一员,随着 Java EE 6 规范一起发布.该版本在前一版本(Servlet 2.5)的基础上提供了若干新特性用于简化 Web 应用的开发和部署.其中有几项特性的引入让开发者感到非常兴奋,同时也获得了 Java 社区的一片赞誉之声: 异步处理支持:有了该特性,Servlet

ios新特征 ARC详解

IOS ARC 分类: IOS ARC2013-01-17 09:16 2069人阅读 评论(0) 收藏 举报 目录(?)[+] 关闭工程的ARC(Automatic Reference Counting) 顺带附上ARC教程 本文部分实例取自iOS 5 Toturail一书中关于ARC的教程和公开内容,仅用于技术交流和讨论.请不要将本文的部分或全部内容用于商用,谢谢合作. 欢迎转载本文,但是转载请注明本文出处:http://www.onevcat.com/2012/06/arc-hand-by

C#4.0新特性之协变与逆变实例分析

本文实例讲述了C#4.0新特性的协变与逆变,有助于大家进一步掌握C#4.0程序设计.具体分析如下: 一.C#3.0以前的协变与逆变 如果你是第一次听说这个两个词,别担心,他们其实很常见.C#4.0中的协变与逆变(Covariance and contravariance)有了进一步的完善,主要是两种运行时的(隐式)泛型类型参数转换.简单来讲,所谓协变(Covariance)是指把类型从"小"升到"大",比如从子类升级到父类:逆变则是指从"大"变到

新辰:详解首页被K后SEOer必做的三大排除方法!

近段时间,有很多朋友向新辰抱怨说出大问题了,为神马site不到首页了,而且收录变成了0?唉,新辰不得不很同情的告诉你:你的首页真的被K了!好了,作为一个职业SEOer,面对被K犹如已经看破红尘般没了脾气,所以,废话少说,身为SEOer的你赶紧补救吧!希望新辰的这些方法能够帮助你早日逃离拔毛的痛苦哦: 所谓治病先察言观色,后开方救人,新辰说的就是先找准病因,才能给出解决的办法.面对突然site不到首页的情况,请先保持冷静哦,然后需要先查看一下收录和排名怎么样了,索引量如何,蜘蛛日志的返回代码是多少