zoukankan      html  css  js  c++  java
  • C#高级编程9-第8章 委托、lamdba表达式和事件

    委托、lamdba表达式和事件


    1.引用方法

    函数指针是一个指向内存位置的指针,不是类型安全的。无法判断实际指向。参数和返回类型也无从知晓。
    .NET委托是类型安全的。定义了返回类型和参数类型,不仅包含方法引用,还可以包含多个方法引用。

    2.委托

    使用方法作为参数进行传递,必须把方法细节进行封装到一个新类型的对象中,即委托。
    委托是一种特殊类型的对象。我们之前定义的对象都包含数据。而委托包含的是多个方法的地址。

    声明委托

    委托使用delegate声明。通过指定返回类型、签名以及参数类型进行创建。

    创建委托的一个或多个实例,编译器将在后台创建表示该委托的一个类。

    delegate void InitMethodInvoker(int x);

    该委托方法无返回值,参数类型是int,每个实例都会有这个方法的引用。该委托类似于方法的定义,没有方法体。

    委托可以使用访问修饰符进行修饰:

    private delegate void InitMethodInvoker(int x);

    定义委托后,创建它的实例,从而实现它的细节即方法体。

    使用委托
    class MathOperations
        {
            public static double MultiplayByTwo(double value)
            {
                return value * 2;
            }
    
            public static double Square(double value)
            {
                return value * value;
            }
        }
    //这个类中定义了委托的实例
    public delegate double DoubleOp(double x);//声明委托
    static void Main(string[] args)
    {
         DoubleOp[] operations = 
         {
             MathOperations.MultiplayByTwo,//指定委托实例方法
             MathOperations.Square
         };
         for (int i = 0; i < operations.Length; i++)
         {
          //将委托实例方法作为参数传递 ProcessAndDisplayNumber(operations[i],
    2.0);//operations[i]是委托即方法参数 ProcessAndDisplayNumber(operations[i], 15); } } static void ProcessAndDisplayNumber(DoubleOp action,double value) { double result = action( value);//实现委托 Console.WriteLine("参数值value:" + value + ",结果值result:" + result); }

    通常情况下需要做安全措施。如果这个方法使用DoubleOp委托实例方法作为参数传递,如果传入null值,到了action( value)会出现异常。因此需要在方法里面加上判断

    ProcessAndDisplayNumber(null, 15);
    static void ProcessAndDisplayNumber(DoubleOp action,double value)
    {
      if(action!=null)
         double result = action( value);//实现委托
       Console.WriteLine("参数值value:" + value + ",结果值result:" + result);
    }
    系统委托

    系统委托有4中:Action类、Func类、Predicate<T>、Comparison<T>委托

    Action类的委托
    1. Action委托 封装一个方法,该方法不具有参数并且不返回值
    2. Action<T>委托 封装一个方法,该方法只有一个参数并且不返回值

    3. Action<T1,T2>委托 封装一个方法,该方法具有两个参数并且不返回值
    4. 复制代码
      static void Main(string[] args)
      {
        #region Action<T>委托示例
        //需求:打印出整型集合list的元素
        List<int> list = new List<int>() { 1, 2, 3, 4, 5 };
        //将匿名方法分配给 Action<T> 委托实例
        Action<int> concat1 = delegate(int i) { Console.WriteLine(i); };
        list.ForEach(concat1);
        //将 lambda 表达式分配给 Action<T> 委托实例
        Action<int> concat2 = (i => Console.WriteLine(i));
        list.ForEach(concat2);
        Console.ReadKey();
        #endregion 
      }
      复制代码

    Func类的委托

    1. 1.Func(TResult)委托封装封装一个不具有参数但却返回 TResult 参数指定的类型值的方法
    2. Func(T,TResult)委托 封装一个具有一个参数并返回 TResult 参数指定的类型值的方法
    3. Func(T1,T2,TResult)委托 封装一个具有两个参数并返回 TResult 参数指定的类型值的方法
    4. 复制代码
      static void Main(string[] args)
      {
        #region Func<T,TResult>委托示例
        //需求:查找整型集合list中大于3的所有元素组成的新集合,并打印出集合元素
        List<int> list = new List<int>() { 1, 2, 3, 4, 5 };
        //将匿名方法分配给 Func<T,TResult> 委托实例
        Func<int, bool> concat1 = delegate(int i) { return i > 3; };
        var newlist1 = list.Where(concat1).ToList();
        //将 Lambda 表达式分配给 Func<T,TResult> 委托实例
        Func<int, bool> concat2 = i => i > 3;
        var newlist2 = list.Where(concat2).ToList();
        newlist1.ForEach(i => Console.WriteLine(i.ToString()));
        newlist2.ForEach(i => Console.WriteLine(i.ToString()));
        Console.ReadKey();
        #endregion
      }
      复制代码

    Predicate<T>委托

        表示定义一组条件并确定指定对象是否符合这些条件的方法

    1. 复制代码
      static void Main(string[] args)
      {
        #region Predicate<T>委托示例
        //需求:查找整型集合list中大于3的所有元素组成的新集合,并打印出集合元素
        List<int> list = new List<int>() { 1, 2, 3, 4, 5 };
        //将匿名方法分配给 Predicate<T> 委托实例
        Predicate<int> concat1 = delegate(int i) { return i > 3; };
        var newlist1 = list.FindAll(concat1);
        //将 lambda 表达式分配给 Predicate<T> 委托实例
        Predicate<int> concat2 = (c => c > 3);
        var newlist2 = list.FindAll(concat2);
        newlist1.ForEach(i => Console.WriteLine(i));
        newlist2.ForEach(i => Console.WriteLine(i));
             Console.ReadKey();
        #endregion
      }
      复制代码

    Comparison<T>委托

       表示比较同一类型的两个对象的方法

    • 复制代码
      static void Main(string[] args)
      {
        #region Comparison<T>委托示例
        //需求:将整型集合list中的所有元素倒序排列打印出来
        List<int> list = new List<int>() { 1, 2, 3, 4, 5 };
        //将匿名方法分配给 Comparison<T> 委托实例
        Comparison<int> concat1 = delegate(int i, int j) { return j - i; };
        //将 lambda 表达式分配给 Comparison<T> 委托实例
        Comparison<int> concat2 = (i, j) => j - i;
        list.Sort(concat1);
        list.ForEach(c => Console.WriteLine(c.ToString()));
        list.Sort(concat2);
        list.ForEach(c => Console.WriteLine(c.ToString()));
             Console.ReadKey();
        #endregion
      }
      复制代码
    BubbleSorter

    说明了委托真正的意图,首先定义一个Employee类,类中定义静态方法CompareSalary

    public static bool CompareSalary(Employee e1, Employee e2)
    {
          return e1.Salary < e2.Salary;
    }

    然后我们再定义一个类BubbleSorter,类中定义静态方法Sort

    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 temp = sortArray[i];
                            sortArray[i] = sortArray[i + 1];
                            sortArray[i + 1] = temp;
                            swapped = true;
                        }
                    }
                } while (swapped);
            }

    接下来我们分析一下:

    Func<T, T, bool>是系统定义的委托,该委托具有2个参数,一个返回值,委托参数类型T根据调用Sort方法时进行指定(BubbleSorter.Sort<Employee>(..,..,))。然后我们看看所有执行代码:
    static void Main()
            {
                Employee[] employees =
                {
                    new Employee("Bugs Bunny", 20000),
                    new Employee("Elmer Fudd", 10000),
                    new Employee("Daffy Duck", 25000),
                    new Employee("Wile Coyote", 1000000.38m),
                    new Employee("Foghorn Leghorn", 23000),
                    new Employee("RoadRunner", 50000)
                };
    //将Employee.CompareSalary方法作为参数进行传递,记住Employee.CompareSalary是一个委托实例类型,它目前不属于Func<T, T, bool>的实例,但是
    //它符合Func<T, T, bool>类型,因此可以作为Func<T, T, bool>的实例进行参数传递。
          BubbleSorter.Sort(employees, Employee.CompareSalary);
          foreach (var employee in employees) 
          {
            Console.WriteLine(employee);
          }
    }
    多播委托

    委托可以包含多个方法,可以多次显式调用这个委托。

    需要注意的是,多播委托需要连续的调用多个方法,并且委托的返回结构是void,否则就只能得到最后一个方法的结果。

    可以使用“+=”或者“-=”添加和删除方法。

    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);
        }
      }
    View Code
    static void Main()
            {
                Action<double> operations = MathOperations.MultiplyByTwo;//   value*2
                operations += MathOperations.Square;//value*value
    
                ProcessAndDisplayNumber(operations, 2.0);
                ProcessAndDisplayNumber(operations, 7.94);
                ProcessAndDisplayNumber(operations, 1.414);
                Console.WriteLine();
            }
    
            static void ProcessAndDisplayNumber(Action<double> action, double value)
            {
                Console.WriteLine();
                Console.WriteLine("ProcessAndDisplayNumber called with value = {0}", value);
                action(value);
    
            }
    View Code
    ProcessAndDisplayNumber called with value = 2
    Multiplying by 2: 2 gives 4
    Squaring: 2 gives 4
    
    ProcessAndDisplayNumber called with value = 7.94
    Multiplying by 2: 7.94 gives 15.88
    Squaring: 7.94 gives 63.0436
    
    ProcessAndDisplayNumber called with value = 1.414
    Multiplying by 2: 1.414 gives 2.828
    Squaring: 1.414 gives 1.999396

    虽然可以执行多个方法,但是如果执行到其中一个方法时失败了,那么后面的方法得不到执行。此时需要一个解决办法。

    .NET中的系统委托定义了一个方法:GetInvocationList()通过这个方法能够得到所有需要执行的委托方法。然后我们迭代一下就可以处理其中一个方法失败,其他方法继续运行。

    Action d1 = One;
    d1 += Two;
    
    Delegate[] delegates = d1.GetInvocationList();
    foreach (Action d in delegates)
    {
        try
        {
          d();
        }
        catch (Exception)
        {
           Console.WriteLine("Exception caught");
        }
    }
    static void One()
    {
          Console.WriteLine("One");
          throw new Exception("Error in one");
    }
    
    static void Two()
    {
          Console.WriteLine("Two");
    }
    View Code
    匿名方法

    原来使用委托,我们需要定义委托的委托的方法实例。现在我们需要简化它的操作,直接使用delegate关键字声明并定义。

    在后面使用lamdba表达式之后会进行再次简化工作。使委托得到了更加灵活广泛的使用。

    static void Main()
    {
         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"));
    
    }

    3.lamdba表达式

    只要有委托的地方均可以使用lamdba表达式。声明委托的方法将变得更简单。

    使用param =>代替了delegate(string param)的声明

    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"));

    结果:

    start of string,middle part,and this was added to the string
    参数

    lamdba表达式多个参数进行定义声明

    下面的委托(x, y)=>x*y是委托Func<double, double, double>的实例,

    如果委托只有一个参数的话直接使用x=>y;就可以了;代表实现了Func<double,double>

    如果委托有一个以上参数的话需要用括号包起来;=>指定了返回值。

    如果需要在定义时指定参数的类型,也可以将参数的数据类型加上。

    Func<double, double, double> twoParams = (x, y) => x * y;
    Console.WriteLine(twoParams(3, 2));
    
    Func<double, double, double> twoParamsWithTypes = (double x, double y) => x * y;
    Console.WriteLine(twoParamsWithTypes(4, 2));
    多行代码

    对于简单的(x, y) => x * y的表达式,编译器会给一条隐式的return返回语句。如果这个委托里面需要做的事情不仅仅是x*y这么简单呢,我们该如何定义?

    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表达式可以访问lambda表达式外部的变量,就是闭包。

    someVal是lambda表达式外的变量,在表达式内部进行了访问。

    int someVal = 5;
    Func<int, int> f = x => x + someVal;

    实际上对于表达式 x => x + someVal;编译器给它定义了一个匿名类;类中定义了有参构造方法,外部变量作为构造方法的参数传入到这个类中进行访问的。

    public class AnonymousClass
        {
            private int someVal;
            public AnonymousClass(int someVal)//外部变量作为构造函数的参数。
            {
                this.someVal = someVal;
            }
            public int AnonymousMethod(int x)
            {
                return x + someVal;
            }
        }
    foreach闭包

    主要是说明闭包在C#4.0和C#5.0对这个的改变。

    var values = new List<int>() { 10, 20, 30 };
    var funcs = new List<Func<int>>();
    
    foreach(var val in values)
    {
        funcs.Add(() => val);
    }
    
    foreach(var f in funcs)
    {
        Console.WriteLine(f);
    } 

    在C#4.0中会输出30,30,30而在C#5.0中会输出10,20,30

    我们回到C#高级编程9的数组中看到之前有说明foreach的原始逻辑:

    foreach (var p in persons)
    {
       Console.WriteLine(p);
    }
    //通过IL中间语言生成后:
    IEnumerator<Person> enumerator = persons.GetEnumerator();
    while (enumerator)
    {
        Person p = enumerator.Current;
       Console.WriteLine(p);
    }

    实际上在C#4.0中enumerator.Current是定义在循环外部的,每次迭代都使用这个值。循环结束该变量就是最后的值。

    因此在C#4.0中需要将 funcs.Add(() => val);改为var v=val;funcs.Add(() => v);

    4.事件

    事件基于委托,为委托提供了发布和订阅机制。

    事件发布程序和侦听器

    首先我们看看这个信息:

    交通工具: 巴士
    乘客甲: 乘坐了 巴士
    交通工具: 的士
    乘客甲: 乘坐了 的士
    乘客乙: 乘坐了 的士
    交通工具: 地铁
    乘客乙: 乘坐了 地铁

    首先可以看出通过程序执行了“交通工具: 巴士”,然后执行了“乘客甲: 乘坐了 巴士”;

    接下来看这块代码:

    var dealer = new CarDealer();
    var michael = new Consumer("乘客甲");
    
    WeakEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", michael.NewCarIsHere);
    dealer.NewCar("巴士");
    CarDealer和Consumer分别是2个对象,
    WeakEventManager 提供基本类中使用的事件管理 弱事件模式。 该管理器添加和移除的事件 (或回调) 也使用该模式的侦听器。
    下面是
    AddHandler方法的定义:
    public static void AddHandler(TEventSource source, string eventName, EventHandler<TEventArgs> handler);
    source事件源;eventName是事件源对象里面的属性也就是事件名.handler是处理事件

    执行的逻辑是这样的:

     WeakEventManager.AddHandler做的事情是:

      1)指定source对象中必须包含eventName属性,如果不存在该属性,会抛出异常。

      2)为eventName属性定义了实现handler;也就是说 Func<string, EventArgs> sourceEvent = eventName => handler;

     以这个为例:

    WeakEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", michael.NewCarIsHere);
    dealer.NewCar("巴士");

       我们现在来想象一下CarDealer对象的定义:

     1)首先必须存在一个类,类中必须有一个类型为EventHandler<TEventArgs>的属性,属性名必须是NewCarInfo,需要记住的是该属性是一个事件。

     2)AddHandler方法为NewCarInfo属性指定了实现方法michael.NewCarIsHere,接下来就是进行调用NewCarInfo

       3)我们定义一个方法NewCar,使用NewCar来调用NewCarInfo,那么如何调用NewCarInfo呢?请看下EventHandler<TEventArgs>需要传入哪些参数

    public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

       4)由此我们得出了,再调用NewCarInfo方法时需要传入2个参数。sender是触发的事件对象,我们可以将dealer对象即NewCar方法中的this,还有一个就是TEventArgs e事件,这个事件也没有,这个事件是做什么用的呢,

      主要是提供事件的一些信息。接下来我们定义这个类为CarInfoEventArgs,当然这个类必须继承TEventArgs ,否则不能作为NewCarInfo方法参数。然后我们提供一个Car的字符串属性,用于记录事件信息。

     5)事件信息类也定义好了,我们需要调用NewCarInfo方法了,我们传入this还有new CarInfoEventArgs()对象。为了记录Car信息,我们将CarInfoEventArgs构造方法进行改造,传入Car参数。

       6)然后我们就有了调用:

    if (NewCarInfo != null)
    {
        NewCarInfo(this, new CarInfoEventArgs(car));
    }

       7)实际上到此为止,核心逻辑都写完了,但是还漏了一步。我们一直在说:NewCarInfo方法的调用和实现,但是NewCarInfo具体实现还没有。

     8)我们再写一个类,类里面需要包含一个方法,方法必须符合EventHandler<TEventArgs>委托类型

    public void NewCarIsHere(object sender, CarInfoEventArgs e)
    {
          Console.WriteLine("{0}: 乘坐了 {1}", name, e.Car);
    }

       9)我们已经有了sender和e的值即第6点中的: this和new CarInfoEventArgs(car)

     10)现在可以调用了。不好意思现在可以揭露代码的真相了.如果没有理解上面说的含义,没有关系,把下面的代码放到程序里面,调试一下吧。你就明白了。

    static void Main(string[] args)
    {
                var dealer = new CarDealer();
    
                var michael = new Consumer("乘客甲");
                WeakEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", michael.NewCarIsHere);
    
                dealer.NewCar("巴士");
    }
    运行
    public class CarInfoEventArgs : EventArgs
        {
            public CarInfoEventArgs(string car)
            {
                this.Car = car;
            }
    
            public string Car { get; private set; }
        }
    
        public class CarDealer
        {
            public event EventHandler<CarInfoEventArgs> NewCarInfo;
            public CarDealer()
            {
    
            }
    
            public void NewCar(string car)
            {
                Console.WriteLine("交通工具: {0}", car);
                if (NewCarInfo != null)
                {
                    NewCarInfo(this, new CarInfoEventArgs(car));
                }
            }
        }
    事件发布
    public class Consumer 
      {
        private string name;
    
        public Consumer(string name)
        {
          this.name = name;
        }
    
        public void NewCarIsHere(object sender, CarInfoEventArgs e)
        {
          Console.WriteLine("{0}: 乘坐了 {1}", name, e.Car);
        }
    
    
    
      }
    事件侦听

     现在结果出来了

    交通工具: 巴士
    乘客甲: 乘坐了 巴士

    接下来修改一下运行代码:

    static void Main(string[] args)
    {
         var dealer = new CarDealer();
    
         var michael = new Consumer("乘客甲");
         WeakEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", michael.NewCarIsHere);
    
         dealer.NewCar("巴士");
    
         var sebastian = new Consumer("乘客乙");
         WeakEventManager<CarDealer, CarInfoEventArgs>.AddHandler(dealer, "NewCarInfo", sebastian.NewCarIsHere);
    
         dealer.NewCar("的士");
    
         WeakEventManager<CarDealer, CarInfoEventArgs>.RemoveHandler(dealer, "NewCarInfo", michael.NewCarIsHere);
    
         dealer.NewCar("地铁");            
    }

    给NewCarInfo事件多添加了一个事件处理(事件的实现)sebastian.NewCarIsHere

    事件还是原来的事件NewCarInfo,AddHandler()方法除了给NewCarInfo事件指定了事件处理,还可以为该事件添加多个事件处理。

    RemoveHandler()方法用来移除所添加的事件处理。必须指定事件对象dealer,事件名,以及事件处理,如果提供的事件处理不存在该事件对象中,将不会移除。

    弱事件

    通过事件、直接连接到发布程序和侦听器。垃圾回收有问题,如:侦听器不再直接引用。发布程序仍有一个引用。垃圾回收器不能清空侦听器占用的内存。

    这种强连接可以使用弱事件解决。使用WeakEventManager作为发布程序和侦听器的中介。我们已经理解了关于发布和侦听器,下面的就好理解了。

    class Program
      {
        static void Main()
        {
          var dealer = new CarDealer();
    
          var michael = new Consumer("Michael");
          WeakCarInfoEventManager.AddListener(dealer, michael);
    
          dealer.NewCar("Mercedes");
    
          var sebastian = new Consumer("Sebastian");
          WeakCarInfoEventManager.AddListener(dealer, sebastian);
    
          dealer.NewCar("Ferrari");
    
          WeakCarInfoEventManager.RemoveListener(dealer, michael);
    
          dealer.NewCar("Red Bull Racing");
        }
      }
    程序入口
    public class WeakCarInfoEventManager : WeakEventManager
      {
        public static void AddListener(object source, IWeakEventListener listener)
        {
          CurrentManager.ProtectedAddListener(source, listener);
        }
    
        public static void RemoveListener(object source, IWeakEventListener listener)
        {
          CurrentManager.ProtectedRemoveListener(source, listener);
        }
    
        public static WeakCarInfoEventManager CurrentManager
        {
          get
          {
            WeakCarInfoEventManager manager = GetCurrentManager(typeof(WeakCarInfoEventManager)) as WeakCarInfoEventManager;
            if (manager == null)
            {
              manager = new WeakCarInfoEventManager();
              SetCurrentManager(typeof(WeakCarInfoEventManager), manager);
            }
            return manager;
          }
        }
    
    
        protected override void StartListening(object source)
        {
          (source as CarDealer).NewCarInfo += CarDealer_NewCarInfo;
        }
    
        void CarDealer_NewCarInfo(object sender, CarInfoEventArgs e)
        {
          DeliverEvent(sender, e);
        }
        protected override void StopListening(object source)
        {
          (source as CarDealer).NewCarInfo -= CarDealer_NewCarInfo;
        }
      }
    弱事件类
    public class Consumer : IWeakEventListener
      {
        private string name;
    
        public Consumer(string name)
        {
          this.name = name;
        }
    
        public void NewCarIsHere(object sender, CarInfoEventArgs e)
        {
          Console.WriteLine("{0}: car {1} is new", name, e.Car);
        }
    
        bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
        {
          NewCarIsHere(sender, e as CarInfoEventArgs);
          return true;
        }
    
    
      }
    侦听器
    public class CarInfoEventArgs : EventArgs
      {
        public CarInfoEventArgs(string car)
        {
          this.Car = car;
        }
    
        public string Car { get; private set; }
      }
    
      public class CarDealer
      {
        public event EventHandler<CarInfoEventArgs> NewCarInfo;
    
        public CarDealer()
        {
    
        }
    
        public void NewCar(string car)
        {
          Console.WriteLine("CarDealer, new car {0}", car);
          if (NewCarInfo != null)
          {
            NewCarInfo(this, new CarInfoEventArgs(car));
          }
        }
      }
    发布程序

    执行过程:

    注册事件>添加事件>发布程序>事件侦听>执行事件
    /*
     1)WeakCarInfoEventManager.AddListener
     2)WeakCarInfoEventManager.StartListening(object source)
     3)dealer.NewCar("Mercedes");
     4)NewCarInfo(this, new CarInfoEventArgs(car))
     5)WeakCarInfoEventManager.CarDealer_NewCarInfo(object sender, CarInfoEventArgs e)
     6)Consumer.ReceiveWeakEvent
     7)Consumer.NewCarIsHere
     8)WeakCarInfoEventManager.CarDealer_NewCarInfo执行完成
     */
    运行过程
     

    @author duanlaibao

    @help C# Advanced programming.Nine

    @date 13:31:36

  • 相关阅读:
    自学mvc4.0 工作当中随笔(在view页面当中循环table,当遇到html标签怎么处理)
    安卓当中的线程和每秒刷一次
    通过后台代码访问前台js
    学习正则表达式记录
    net 当中动态给记事本当中插入值
    session,cookie 等区别
    vss 2.0框架与4.0框架的设置iis区别
    Javascript中char和int的互相转换的代码(转载)
    获得鼠标在页面上的坐标
    HTTP协议header头域
  • 原文地址:https://www.cnblogs.com/licin/p/6840531.html
Copyright © 2011-2022 走看看