【学习笔记】C#

第一章 C#概述:

1、CLR的概念和功能

CLR是一个可以由多编程语言使用的运行时。
CLR的核心功能:内存管理,程序集加载,安全性,异常处理,线程同步等等。可以被很多属于微软系列的开发语言使用。

2、 C#的优势和特点

特点:跨平台、跨语言、跨标准
优势:适用于windows平台

3、 C#开发.NET应用程序的步骤

C#开发。NET应用程序的步骤

4、 C#源程序文件、vs解决方案文件、Web项目文件、C#项目文件的后缀名

扩展名

5、 第一个简单的C#应用程序

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

第二章 C#编程基础

1、System.Array的属性与方法

System.Array是所有数组类型的抽象基类型,所有的数组类型均由它派生,任何数组都可以使用System.Array具有的属性及方法
System.Array

2、一维数组的声明、创建、初始化

//声明
int a[];
//创建
a = new int[4];
int a = new int[4];
const int i = 4; int a = new int[i];
//初始化
for (int i = 0; i < fs.Length; i++)
	fs[i]=i;
type[] arrayName = new type[size]{val1,val2,,valn};
type[] arrayName = new type[] { val1, val2,, valn };
float[] fs = { 21.34f, 37.58f, 776.46f, 834.23f };
float[] fs;
fs = new float[] { 21.34f, 37.58f, 776.46f, 834.23f };

3、规则与锯齿状二维数组的声明、创建、初始化

//声明
数组元素类型[,] 数组名; Point[,] ps;
数组元素类型[][] 数组名; Point[][] ps;
//创建
数组名 = new 数组元素类型[一维元素个数][二维元素个数];
ps = new Point[4][]; //锯齿状二维数组
数组名 = new 数组元素类型[一维元素个数, 二维元素个数];
ps = new Point[4, 5]; //规则二维数组
//初始化
Point[][] ps = new Point[3][]; //锯齿状二维数组的初始化
for (int i = 0; i < ps.GetLength(0); i++)
{
	ps[i] = new Point[5];
	for (int j = 0; j < ps[i].Length; j++)
		ps[i][j] = new Point(i + 1, j + 1);
}
Point[,] ps = new Point[3, 4]; //规则二维数组的初始化
for (int i = 0; i < ps.GetLength(0); i++)
	for (int j = 0; j < ps.GetLength(1); j++)
		ps[i][j] = new Point(i + 1, j + 1);
		
int[,] a = new int[2, 3] { { 0, 1, 2 }, { 3, 4, 5 } };

int[,] a = new int[,] { { 0, 1, 2 }, { 3, 4, 5 } };

int[,] a = { { 0, 1, 2 }, { 3, 4, 5 } };

int[,] a; a = new int[2, 3] { { 0, 1, 2 }, { 3, 4, 5 } };

char[][] st1 = new char[3][];
st1[0] = new char[] {‘S’,’e’,’p’,’t’,’e’,’m’,’b’,’e’,’r’};
st1[1] = new char[] {‘O’,’c’,’t’,’o’,’b’,’e’,’r’};
st1[2] = new char[] { ‘N’,’o’,’v’,’e’,’m’,’b’,’e’,’r’};

4、数组的Length属性

一维数组:数组名.Length 是数组元素的个数
二维数组:
数组名.Length 是规则二维数组含有的总元素的个数
int[,] c = new int[3, 4]; //c.Length为12
数组名.Length 是锯齿状二维数组含有的一维数组的个数
int[][] c = new int[3][]; //c.Length为3

5、foreach语句

//foreach (类型 标识符 in 集合表达式)
	//语句;
int[] ints = { 1, 2, 3 };
foreach (int temp in ints)
	Console.WriteLine(temp);

6、动态数组ArrayList

  • 需引入System.Collections命名空间
  • Array与ArrayList的区别
  • Array的容量固定,而ArrayList可以动态扩充
  • ArrayList提供添加、插入或移除某一范围元素的方法。在Array中,只能一次获取或设置一个元素的值
  • Array可以是多维的,而ArrayList是一维的
ArrayList arr = new ArrayList();
string str1;
str1 = Console.ReadLine();
arr.Add(str1);

第三章 面向对象编程基础

1、属性及属性的修饰符

  • 属性的定义
//定义属性结构
public int MyIntProp{
	get{
		//get code
	}
    set{
            //set cod
	}
}
  1. 定义属性需要名字和类型。
  2. 属性包含两个块:get块和set块。
  3. 访问属性和访问字段一样,当取得属性的值得时候,就会调用属性中的get块,因此get块需要返回值,其返回值类型就是属性的类型;当我们去给属性设置值得时候,就会调用属性中的set块,以此可以在set块中通过value访问到我们所设置的值。
  4. 属性的种类和修饰符
//只读
private string name;
public string Name{
    get{
            return name;
	}
}
//只写
private string name;
public string Name{
    set{
            name = value;
	}
}
//可读写
private string name;
public string Name{
    get{
            return name;
	}
	set{
            name = value;
	}
}
//static属性:只能访问静态数据
public static int LoginCount {
	get{
		return m_LoginCount;//m_LoginCount为静态数据
	}
}
//如果在get或set块钱加上private,表示这个块只能在类内进行调用
public float X{
    private set { x = value;}  
    get { return x;}
}
public float X{
    set { x = value;}  
    private  get { return x;} 
}
//自动实现的属性
public int Age{set;get;}    //编译器会自动提供字段来存储age
-->等价于
public int Age{
    set{ age = value;}
    get{ return age;}

2、属性与字段的区别和联系

  • 均可以访问对象中包含的数据
  • 属性不能直接访问数据
  • 属性可以限制数值的取值范围
  • 属性可设置为仅读、仅写、可读写
  • 属性可以象方法一样使用virtual、abstract、override、new、static等修饰符,而这些修饰符不能用于字段

3、抽象属性

abstract class A {
    protected int x;

    public abstract int X {
        get;
        set;
    }
}
class B : A{
    public B(int a) {
        x = a;
    }
    public override int X {
        get {
            Console.WriteLine("in class B property X get block");
            return x;
        }
        set {
            Console.WriteLine("in class B property X set block");
            x = value;
        }
    }
}

4、类的修饰符

类修饰符
- 其他修饰符

  • abstract 抽象类,含有抽象成员,因此不能被实例化,只能用作基类
  • sealed 密封类,不能从该类派生出其他类

5、方法的修饰符

方法修饰符
- 其他修饰符

  • new
  • static
  • virtual
  • abstract
  • override
  • sealed

6、修饰符的无效组合

无效

7、集合与foreach循环

- 集合

  • .NET提供实现集合的接口,包括IEnumerable,ICollection,IList,IDictionary, IDictionaryEnumerator, IComparer等,只需继承实现集合接口
  • 也可以直接使用.NET已经定义的集合类,包括Array, ArrayList, Queue, Stack, BitArray,Hashtable
using System;
using System.Collections;

namespace CustomCollection
{
    //定义集合中的元素所属的类
    class MyClass
    {
        public string Name;
        public int Age;
        public MyClass(string name, int age)
        {
            this.Name = name;
            this.Age = age;
        }
    }
    public class Iterator : IEnumerator, IEnumerable
    {
        private MyClass[] ClassArray;
        int Cnt;
        public Iterator()
        {
            ClassArray = new MyClass[4];
            ClassArray[0] = new MyClass("Kith", 23);
            ClassArray[1] = new MyClass("Smith", 30);
            ClassArray[2] = new MyClass("Geo", 19);
            ClassArray[3] = new MyClass("Greg", 14);
            Cnt = -1;
        }
        //实现IEnumerator的Reset()方法
        public void Reset()
        {
            Cnt = -1;
        }
        //实现IEnumerator的MoveNext()方法
        public bool MoveNext()
        {
            return (++Cnt < ClassArray.Length);
        }
        //实现IEnumerator的Current属性
        public object Current
        {
            get
            {
                return ClassArray[Cnt];
            }
        }
        //实现IEnumerable的GetEnumerator()方法
        public IEnumerator GetEnumerator()
        {
            return (IEnumerator)this;
        }
        static void Main()
        {
            Iterator It = new Iterator();
            foreach (MyClass mc in It)
            {
                Console.WriteLine("Name: " + mc.Name);
                Console.WriteLine("Age: {0}", mc.Age);
            }
        }
    }
}

  • foreach循环隐藏调用IEnumerable的GetEnumerator方法得到Iterator,并将集合指向-1的位置
  • foreach循环反复调用MoveNext方法移动集合,使用Current获取集合当前元素

8、索引器

索引器

class IndexClass //带索引器的类(下标为整数)
{
    private string[] name = new string[10];
    public string this[int index]
    {
        get { return name[index]; }
        set { name[index] = value; }
    }
}
// 索引器的使用
        IndexClass b = new IndexClass();
        b[0] = "张三";
        b[1] = "李四";
        b[2] = "王五";
        Console.WriteLine("b[0]=" + b[0]);
        Console.WriteLine("b[1]=" + b[1]);
        Console.WriteLine("b[2]=" + b[2]);
class IndexClass //带索引器的类(下标为字符串)
{
    private Hashtable name = new Hashtable();
    public string this[string index]
    {
        get { return name[index].ToString(); }
        set { name.Add(index, value); }
    }
}
// 索引器的使用
        IndexClass b = new IndexClass();
        b["A001"] = "张三";
        b["A002"] = "李四";
        b["A003"] = "王五";
        Console.WriteLine("b[\"A001\"]=" + b["A001"]);
        Console.WriteLine("b[\"A002\"]=" + b["A002"]);
        Console.WriteLine("b[\"A003\"]=" + b["A003"]);
//索引器可重载
public string this[int index] {//A索引器
        get { return name[index].ToString(); }
        set { name.Add(index, value); }
}
public int this[string aName] {//B索引器
        get {
            foreach (DictionaryEntry d in name) {
                if (d.Value.ToString() == aName)
                    return Convert.ToInt32(d.Key);
            }
            return -1;
        }
        set { name.Add(value, aName); }
}

9、索引器与数组的不同点 索引器与属性的不同点

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

//多个参数索引器
public int this[string name, int courseID] {
	get {
		foreach (CourseScore cs in arrCourseScore) {
			if (cs.Name == name && cs.CourseID == courseID) {
                    return cs.Score;
			}
		}
		return -1;
	}
	set {
		arrCourseScore.Add(new CourseScore(name, courseID, value));
	}
}

10、委托 委托应用——异步回调 如何委托 委托与C++函数指针的区别

- 委托的定义
委托是函数的封装,它代表一类函数,它们都符合一定的签名:拥有相同的参数列表和返回值类型。

  • 委托的实例代表一个具体的函数
  • 一旦为委托分配了方法,委托将与该方法具有完全相同的行为
    在这里插入图片描述
  • 委托的定义使用
public delegate int AddDel(int a, int b);//定义委托,返回int,参数int,int
class A {
    public static int AddFunc(int x, int y)//该方法符合委托的签名:返回int,参数int,int
    { return x + y; }
}
class Program {
    static void Main(string[] args) {
        AddDel ad = new AddDel(A.addFunc);//实例化委托
        Console.WriteLine(ad(2, 3));//执行
    }
}
  • 委托的应用
using System;

delegate bool CompareOp(object a, object b);//委托的定义
class BubbleSorter {
    static public void Sort(object[] sortArray, CompareOp co) {//委托作为参数
        for (int i = 0; i < sortArray.Length; i++) {
            for (int j = 0; j < i; j++) {
                if (co(sortArray[j], sortArray[i])) {
                    object temp = sortArray[i];
                    sortArray[i] = sortArray[j];
                    sortArray[j] = temp;
                }
            }
        }
    }
}
class Employee {
    private string name;
    private decimal salary;
    public Employee(string name, decimal salary) {
        this.name = name;
        this.salary = salary;
    }
    public override string ToString() {
        return string.Format(name + ",{0:C}", salary);
    }
    public static bool Compare(object e1, object e2) {//该方法符合委托签名,返回bool,参数object,
        Employee ee1 = (Employee)e1;
        Employee ee2 = (Employee)e2;
        return (ee1.salary > ee2.salary) ? true : false;
    }
}
class Test {
    static void Main(string[] args) {
        Employee[] employees = {new Employee("王平", 2000), new Employee("李明", 1970), 
        						new Employee("孙丽", 1800), new Employee("付新", 1950), 
        						new Employee("林笑", 1900) };
        CompareOp EmployeeCompareOp = new CompareOp(Employee.Compare);
        BubbleSorter.Sort(employees, EmployeeCompareOp);
        for (int i = 0; i < employees.Length; i++)
            Console.WriteLine(employees[i].ToString());
    }
}

11、多播

  • 一次委托可以调用多个方法
  • 通过+和-运算符可以实现多播的增加或减少
  • 将多个方法组合到一个委托中,如果方法的返回值非void,则通过委托调用方法仅得到最后一个方法的返回值,其它的返回值都将丢失
  • 将多个方法组合到一个委托中,会顺序调用委托中的每个方法

在这里插入图片描述

//调用实例方法
WorkerClass wr = new WorkerClass();
SomeDelegate d1 = new SomeDelegate(wr.InstanceMethod);
Console.WriteLine("Invoking delegate InstanceMethod, return={0}", d1(5, "aaa"));
//调用静态方法
SomeDelegate d2 = new SomeDelegate(WorkerClass.StaticMethod);
Console.WriteLine("Invoking delegate StaticMethod, return={0}", d2(5, "aaa"));
//多播
SomeDelegate d3 = d1 + d2;
Console.WriteLine("Invoking delegates d1 and d2(multi-cast),return={0} ", d3(5, "aaa"));
//委托中的方法个数
int num_method = d3.GetInvocationList().Length;
Console.WriteLine("Number of methods referenced by delegate d3: {0}", num_method);
//多播d3减去委托d2
d3 = d3 - d2;
Console.WriteLine("Invoking delegates d1 (multi-cast),return={0} ", d3(5, "aaa"));
//委托中的方法个数
num_method = d3.GetInvocationList().Length;
 Console.WriteLine("Number of methods referenced by delegate d3: {0}", num_method);

12、事件

//System.EventArgs是包含事件数据的类的基类
//MyEventArgs类派生于EventArgs,实现自定义事件数据的功能(自己定义的一个类)
class EventSource {	//发布事件的类
    MyEventArgs EvArgs = new MyEventArgs("触发事件");
    //定义事件委托
    public delegate void EventHandler(object from, MyEventArgs e);
    //定义事件
    public event EventHandler TextOut;
    //激活事件的方法
    public void TriggerEvent() {
        if (TextOut != null)
            TextOut(this, EvArgs);
    }
}
EventSource evsrc = new EventSource();
//CatchEvent的签名要与定义的委托EventHandler的签名要相同
//订阅事件
evsrc.TextOut += new EventSource.EventHandler(CatchEvent);
//触发事件
evsrc.TriggerEvent();
//取消订阅事件
evsrc.TextOut -= new EventSource.EventHandler(CatchEvent);
//触发事件
evsrc.TriggerEvent();

13、一瓶牛奶与一张报纸的故事示例,彼得的故事示例

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
public interface IMilkBox
{
    void PutMilkIn(string s);
}
public interface INewsPaperBox
{
    void PutNewsPaperIn(string s);
}
public class Subscriber : INewsPaperBox, IMilkBox//订购者类
{
    public string name;

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

    public void PutNewsPaperIn(string s)
    {
        Console.WriteLine("{0} 在报箱里收到送来的 {1}", name, s);
    }

    public void PutMilkIn(string s)
    {
        Console.WriteLine("{0} 在奶箱里收到送来的 {1}", name, s);
    }
}

public class NewsPaperSender
{
    // 接口实现 
    protected ArrayList subscribers = new ArrayList();

    public void SendNewsPaper()
    {
        foreach (INewsPaperBox newsPaperBox in subscribers)
            newsPaperBox.PutNewsPaperIn(" 报纸 ");//订阅者们拿报纸
    }

    public void Subscribe(INewsPaperBox newsPaperBox)
    {
        subscribers.Add(newsPaperBox);//放报纸
    }
}

public class MilkSender
{
    // 接口实现 
    protected ArrayList subscribers = new ArrayList();

    public void SendMilk()
    {
        foreach (IMilkBox milkBox in subscribers)
            milkBox.PutMilkIn(" 牛奶 ");
    }
    public void Subscribe(IMilkBox milkBox)
    {
        subscribers.Add(milkBox);
    }
}
class Program
{
    // 功能的实现 
    public static void Main(string[] args)
    {
        // 接口实现 
        MilkSender ms = new MilkSender();
        NewsPaperSender nps = new NewsPaperSender();
        Subscriber s1 = new Subscriber(" 张三 ");
        Subscriber s2 = new Subscriber(" 李四 ");
        Subscriber s3 = new Subscriber(" 王五 ");
        ms.Subscribe(s1);
        ms.Subscribe(s2);
        nps.Subscribe(s2);
        nps.Subscribe(s3);
        ms.SendMilk();
        nps.SendNewsPaper();
        Console.ReadLine();
    }
}
using System;

delegate void WorkStarted();
delegate void WorkProgressing();
delegate int WorkCompleted();

class Worker
{
    public void DoWork()
    {
        Console.WriteLine("工作: 工作开始");
        if (started != null) started();
        Console.WriteLine("工作: 工作进行中");
        if (progressing != null) progressing();
        Console.WriteLine("工作: 工作完成");
        if (completed != null)
        {
            foreach (WorkCompleted wc in completed.GetInvocationList())
            {
                wc.BeginInvoke(null, null);
            }
        }
    }
    public event WorkStarted started;
    public event WorkProgressing progressing;
    public event WorkCompleted completed;
}

class Boss
{
    public int WorkCompleted()
    {
        Console.WriteLine("Better...");
        return 4; /* 总分为10 */
    }
}

class Family
{
    public int WorkCompleted()
    {
        Console.WriteLine("快回家吧,我们想你了!");
        return 10;
    }
}

class Universe
{
    public static void WorkStarted()
    {
        Console.WriteLine("universe know the start of work");
    }
    public static void WorkProgressing()
    {
        Console.WriteLine("universe know the progressing of work");
    }
    public static int WorkCompleted()
    {
        Console.WriteLine("universe say good!");
        return 8;
    }
}

class A
{
    static void Main()
    {
        Worker peter = new Worker();
        Boss boss = new Boss();
        Family f = new Family();
        peter.started += new WorkStarted(Universe.WorkStarted);
        peter.progressing += new WorkProgressing(Universe.WorkProgressing);
        peter.completed += new WorkCompleted(f.WorkCompleted);
        peter.completed += new WorkCompleted(Universe.WorkCompleted);
        peter.completed += new WorkCompleted(boss.WorkCompleted);
        peter.DoWork();
        Console.WriteLine("Main: 工人工作完成");
        Console.ReadLine();
    }
}

14、实例构造函数

[构造函数修饰符] 标识符([参数列表] )
[ : base ( [参数列表] ) ] [ : this ( [参数列表] ) ]
{
构造函数语句块
}

using System;
namespace ConstructorExp
{
    class Base
    {
        public Base(int x)
        {
            Console.WriteLine("Base.Base(int)");//4
            this.x = x;
        }
        private static int InitX()
        {
            Console.WriteLine("Base.InitX()"); //3
            return 1;
        }
        public int x = InitX();
    }
    class Derived : Base
    {
        public Derived(int a)
            : base(a)
        {
            Console.WriteLine("Derived.Derived(int)"); //5
            this.a = a;
        }
        public Derived(int a, int b)
            : this(a)
        {
            Console.WriteLine("Derived.Derived(int,int)"); //6        
            this.b = b;
        }
        private static int InitA()
        {
            Console.WriteLine("Derived.InitA()");//1
            return 3;
        }
        private static int InitB()
        {
            Console.WriteLine("Derived.InitB()");//2
            return 4;
        }
        public int a = InitA();
        public int b = InitB();
    }
    class Program
    {
        static void Main(string[] args)
        {
            Derived b = new Derived(1, 2);
            Console.ReadLine();
        }
    }
}

15、静态构造函数

[静态构造函数修饰符] 标识符()
{
静态构造函数体
}
在这里插入图片描述

16、析构函数

~标识符()
{
析构函数体
}
在这里插入图片描述
在这里插入图片描述

17、运算符重载

在这里插入图片描述

  • C#要求以静态方式声明运算符重载
  • 编译器不能改变二元运算符的参数顺序
 //二元操作符“+”重载
public static Complex operator +(Complex a, Complex b) {
        return new Complex(a.r + b.r, a.v + b.v);
}
//一元操作符“-”重载
public static Complex operator -(Complex a) {
        return new Complex(-a.r, -a.v);
}
//一元操作符“++”重载
public static Complex operator ++(Complex a) {
        double r = a.r + 1;
        double v = a.v + 1;
        return new Complex(r, v);
}

18、用户定义的数据类型转换

  • 隐式类型转换
    在这里插入图片描述
  • 显示类型转换
    在这里插入图片描述
//隐式类型转换,实现double类型转换为Square
public static implicit operator Square(double s) {
	Console.WriteLine("隐式类型转换,实现double类型转换为Square");
	return new Square(s);
}
//显式类型转换,实现Square类型转换为double
public static explicit operator double(Square s) {
	Console.WriteLine("显式类型转换,实现Square类型转换为double");
	return s.side;
}

19、方法的参数(参数数组params的使用)

在这里插入图片描述

public void swap2(ref int x, ref int y)
{
	int temp;
	temp = x;
	x = y;
	y = temp;
}
A a = new A();
a.swap2(ref xx, ref yy);

在这里插入图片描述
在这里插入图片描述

public class A {
        public void MaxMin(out int max, out int min, params int[] a) {
            if (a.Length == 0) {
                max = min = -1;
                return;
            }
            max = min = a[0];
            for (int i = 1; i < a.Length; i++) {
                if (a[i] > max) max = a[i];
                if (a[i] < min) min = a[i];
                a[i]++;
            }
        }
    }
A a = new A();
int maxNumber, minNumber;
a.MaxMin(out maxNumber, out minNumber, 48, 92, 62, 52, 78);
Console.WriteLine("Max={0},Min={1}", maxNumber, minNumber);

第四章 WinForm程序设计

Button

常用属性
Text
按钮上显示的文本包含在Text属性中。如果文本超
出按钮宽度,则换到下一行。可以包含访问键。
FlatStyle
如果把按钮样式设置为Popup,则按钮就显示为平
面,直到用户把鼠标指针移动到它上面为止。此
时,按钮会弹出,显示为正常的3D外观。
Enabled
设置为false,按钮会灰显,单击它不起任何作用
Image
可以指定在一个按钮上显示的图像(位图,图标等)
ImageAlign
设置按钮的图像在什么地方设置
ImageList
AcceptButton
在任何Windows窗体上都可以指定某个Button控件为接受按钮。每当用户按Enter键时,即单击默认按钮,而不管当前窗体上其他哪个控件具有焦点
CancelButton
每当用户按ESC键时,即单击取消按钮,而不管当前窗体上其他哪个控件具有焦点
HelpButton
获取或设置一个值,该值指示是否应在窗体的标题框中显示“帮助”按钮。
常用事件
Click
该控件不支持双击事件

TextBox

常用属性
CausesValidation
该属性设置为true,且该控件获得了焦点时,会引发两个事件:validating和validated。可以处理这些事件,以便验证失去焦点的控件中数据的有效性。这可能使控件永远都不能获得焦点
CharacterCasing
表示文本框是否会改变输入文本的大小写Lower、Normal、Upper
MaxLength
指定输入到文本框中的最大字符长度
Multiline
表示该控件是一个多行控件
PasswordChar
指定是否用密码字符替换在单行文本框中输入的字符
ReadOnly
表示文本是否为只读
ScrollBars
指定多行文本框是否显示滚动条
SelectedText
在文本框中显示的文本
SelectionLength
在文本中选择的字符数
SelectionStart
文本框中被选中文本的开头
WordWrap
指定在多行文本框中,如果一行的宽度超出了控件的宽度,其文本是否应自动换行
AcceptsReturn
获取或设置一个值,该值指示在多行 TextBox控件中按 Enter 键时,是在控件中创建一行新文本还是激活窗体的默认按钮。
AcceptsTab
文本框的常用方法
Clear
AppendText
Copy
Cut
Paste
Select
SelectAll
文本框的常用事件
焦点事件
Enter
Leave
Validating
Validated
键事件
KeyDown
KeyPress
KeyUp
TextChanged事件
文本框的内容发生变化时触发该事件

RadioButton

RadioButton控件的属性
Appearance
如果将该属性设置为Appearance.Button,则RadioButton的外观可以像命令按钮一样
AutoCheck
该属性设置为true, 用户单击单选按钮时,自动被选中或取消选中。否则,必须在Click事件处理程序中手工设置Checked属性
Checked
表示控件是否被选中的标记
CheckAlign
可以改变单选按钮的对齐形式,默认是
ContentAlignment.MiddleLeft
RadioButton控件的事件
CheckedChanged
当RadioButton的选中状态改变时,引发该事件
Click
每次单击RadioButton时引发该事件。连续单击RadioButton两次或多次只改变checked属性一次。如果被单击按钮的AutoCheck属性是false,则该按钮根本不会被选中,只引发click事件

CheckBox

CheckBox控件的属性
CheckState
与RadioButton不同,CheckBox有三种状态:Checked、Indeterminate、Unchecked。复选框的状态是Indeterminate时,表示复选框的当前值是无效的或者在当前环境下没有意义
ThreeState
当属性为false时,用户不能把CheckState属性改为Indeterminate
CheckBox控件的事件
CheckedChanged
当复选框的Checked属性改变时,就引发该事件。注意在复选框中,当ThreeState属性为true时,单击复选框不会改变Checked属性。在复选框由Checked变为indeterminate状态时,就会出现这种情况
CheckStateChanged
当CheckState属性改变时,引发该事件

RichTextBox

常用属性
CanRedo
如果上一个被撤销的操作可以使用Redo恢复,该属性为true
CanUndo
如果可以在RichTextBox上撤销上一个操作,该属性为true,可用于TextBox
RedoActionName
该属性包含通过Redo方法执行的操作名称
DetectUrls
该属性设置为true,可以使控件检测URL并格式化它们
Rtf
对应于Text属性,但包含RTF格式的文本
SelectedRtf
使用该属性可以获取或设置控件中被选中的RTF格式文本,该文本会保留原有的格式化信息
SelectedText
使用该属性可以获取或设置控件中被选中的文本,但所有的格式化信息都会丢失
SelectionAlignment
表示选中文本的对齐方式,可以是Center、Left或right
SelectionBullet
使用该属性可以确定选中的文本是否格式化为项目符号的格式
BulletIndent
指定项目符号的缩进像素值
SelectionColor
该属性可以修改选中文本的颜色
SelectionFont
可以修改选中文本的字体
SelectionLength
可以设置或获取选中文本的长度
SelectionType
该属性包含选中文本的信息,可以确定是选择了一个或多个OLE对象还是仅选择了文本
ShowSelectionMargin
该属性设置为true,在RichTextBox的左边会出现一个页边距,将使用户更易于选择文本
UndoActionName
如果用户选择撤销某个动作,该属性将获取该动作的名称
SelectionProtected
该属性设置为true,可以指定不修改文本的某些部分
RichTextBox控件的事件
LinkedClick
在用户单击文本中的链接时,引发该事件
Protected
在用户尝试修改被保护的文本时,引发该事件
SelectionChanged
在选中文本发生变化时,引发该事件。

ListBox

常用属性
SelectedIndex
表示选中选项的基于0的索引,若选中多项,则是第一项的索引
ColumnWidth
在包含多个列的列表框中指定列的宽度
Items
包含列表框中的所有选项
MultiColumn
获取和设置列表框中列的数目
SelectedIndices
列表框中选中的多项基于0的索引集合
SelectedItem
指选中的选项。若 选中多项,则指第一项
SelectedItems
包含当前选中的所有选项
Sorted
该属性设置为true,可使列表框对它所包含的选项按照字母顺序排序
Text
设置该属性,将搜索匹配该文本的选项,并选择该选项;获取该属性,返回的值是列表中第一个选中的选项
SelectionMode
可以使用ListSelectionMode枚举中的4种选择模式
None 不能选择任何选项
One 一次只能选择一个选项
MultiSimple 可选择多个选项
MultiExtended 可选择多个选项,用户可使用Ctrl、Shift、和箭头键进行选择。

ComboBox

常用方法
public void BeginUpdate ()
当将多项一次一项地添加到 ComboBox 时维持性能。
public void EndUpdate ()
在 BeginUpdate 方法挂起绘制后,该方法恢复绘制 ComboBox 控件。

TreeView

ListView

常用属性
Activation
该属性可以控制用户在列表视图中激活选项的方式
Standard 用户为自己的机器选择的值
OneClick 单击一个选项,激活它
TwoClick 双击一个选项,激活它
Alignment
该属性可以控制列表视图中选项对齐的方式
Default 如果用户拖放一个选项,它将仍位于拖动前的位置
Left 选项与ListView控件的左边界对齐
Top 选项与ListView控件的顶边界对齐
SnapToGrid ListView控件包含一个不可见的网格,选项都放在该网格中
AllowColumnReorder
该属性设置为true,允许用户修改列表视图中列顺序
AutoArrange
该属性设置为true,选项会自动根据Alignment属性排序
当View属性取为LargeIcon或SmallIcon时才有意义
CheckBoxes
该属性设置为true,列表视图中的每个选项会在其左边显示一个复选框。当View属性取为Details或List时才有意义
CheckedIndices, CheckedItems
选中索引和选项的集合
Columns 列集合
FocusedItem
列表视图中有焦点的选项
FullRowSelect
该属性为true,单击选项,整行都被选中
GridLines
该属性设置为true,会在行和列之间绘制网格线,
只有View取Details才有意义
HeaderStyle
Clickable NonClickable None
HoverSelection
该属性设置为true,用户可以把指针放在列表视图的一个选项上以选择它
Items
LabelEdit
该属性设置为true,用户可以在Details视图下编辑第一列的内容
LabelWrap
该属性设置为true,标签会自动换行,以显示所有的文本
LargeImageList
包含大图像,可以在View属性为LargeIcon时使用
MultiSelect
该属性设置为true,用户可以选择多个选项
Scrollable
该属性设置为true,显示滚动条
SelectedIndices, SelectedItems
选中索引和选项的集合
SmallImageList
Sorting
Ascending
Descending
None
StateImageList
包含图像的模板,这些图像模板可用作LargeImageList和SmallImageList图像的覆盖图,表示定制的状态
TopItem
返回列表视图顶部的选项
View
LargeIcon
SmallIcon
List
显示小图标,但总是显示在单列中
Details
在多列中显示项
Tile
显示一个大图标和一个标签,在图标的右边显示子项信息
HideSelection
指示当控件没有焦点时,该控件选定的项是否保持突出显示
ListView控件的方法
public void BeginUpdate ( )
该方法告诉列表视图停止更新,直到调用EndUpdate为止
public void EndUpdate ()
调用该方法,列表视图会显示出所有选项
public void Clear ()
public void EnsureVisible ( int index )
列表视图会滚动以显示指定索引的选项
public ListViewItem GetItemAt ( int x, int y )
返回列表视图中位于x,y的选项
ListView控件的事件
AfterLabelEdit
编辑了标签后,引发该事件
BeforeLabelEdit
用户开始编辑标签前,引发该事件
ColumnClick
单击一个列时,引发该事件
ItemActivate
激活一个选项时,引发该事件

PictureBox

用于显示位图(.bmp)、GIF(.gif)、JPEG(.jpg)、图元文件(.wmf)、图标(.ico)格式的图形
常用属性
Image
指定显示的图片
SizeMode
控制图像和控件彼此适合的方式
Normal 将控件的左上角与控件的左上角对齐
AutoSize 调整控件的大小以适合其显示的图片
CenterImage 图片在控件内居中
StretchImage 拉伸所显示的图片以适合控件

菜单栏

ToolStripMenuItem
常用属性
ShortcutKeys
获取或设置与 ToolStripMenuItem 关联的快捷键。
Image
获取或设置显示的图像
Checked
表示菜单是否被选中
CheckOnClick
该属性为true,如果菜单项左边的复选框没有打上标记,就打上标记,如果该复选框已打上标记,就清除该标记。否则,该标记就被一个图像替代。
Enabled
该属性设置为false,菜单项就会灰显,不能被选中
DropDownItems
返回一个项集合,用作与菜单项相关的下拉菜单
事件
Click
用户单击菜单项时引发该事件
CheckedChanged
单击带CheckedOnClick属性的菜单项时,引发该事件

工具栏

ToolStrip
常用属性
GripStyle
该属性控制移动手柄是可见还是隐藏
ToolStripGripStyle.Hidden Visible
LayoutStyle
控制工具栏上的项如何显示,默认水平显示
ToolStripLayoutStyle.Flow 按水平或垂直方向排列
HorizontalStackWithOverflow 按水平方向进行布局且必要时会溢出
StackWithOverflow 按自动方式进行布局
Table 布局方式为左对齐
VerticalStackWithOverflow 按垂直方向进行布局,在控件中居中且必要时会溢出
Items
工具栏中所有项的集合
ShowItemToolTip
确定是否显示工具栏上某项的工具提示
Stretch
该属性设置为true,工具栏会占据容器的总长
ToolStrip的项
ToolStripButton
表示一个按钮,用于带文本和不带文本的按钮
ToolStripLabel
表示一个标签,还可以显示图像
ToolStripSplitButton
表示一个右端带有下拉按钮的按钮
ToolStripDropDownButton
控件具有下拉数组图像,单击控件的任一部分,都可以打开菜单部分
ToolStripComboBox
组合框
ToolStripProgressBar
进度条
ToolStripTextBox
文本框
ToolStripSeperator
为各个项创建水平或垂直分隔符

状态栏

StatusStrip中可以使用ToolStripDropDownButton、ToolStripProgressBar、ToolStripSplitButton
StatusStripStatusLabel
AutoSize
DoubleClickEnable
可以指定是否引发DoubleClick事件

OpenFileDialog

InitialDirectory属性
文件对话框显示的初始目录
默认情况下,对话框将打开用户在上一次运行应用
程序时打开的目录
默认值是一个空串,表示用户的“我的文档”目录
Filter属性
设置文件过滤器,定义用户可以选择打开的文件类型
每个部分需要有两个字符串,第一个字符串定义要在文本框中显示的文本,第二个字符串用于指定要在对话框中显示的文件扩展名
FilterIndex属性
指定列表框中的默认选项。 基于1
ValidateNames属性
检查用户输入的文件名,看其是否是一个有效的文件名。
该属性设置为true,可以使用CheckFileExists属性验证
文件的有效性,使用CheckPathExists属性验证路径的有效性
FileName属性
包含在文件对话框中选定文件名的字符串
Multiselect属性
该属性设置为true,用户可以选中多个文件
FileNames属性
包含所有选中文件名的字符串。包含的文件顺序与
选中它们的顺序相反。
OpenFileDialog的专用属性
ShowReadOnly属性
指示对话框是否包含只读复选框
ReadOnlyChecked属性
指示是否选定只读复选框
OpenFileDialog的常用方法
public Stream OpenFile ()
打开用户选定的具有只读权限的文件

SaveFileDialog

常用属性
AddExtension
指示如果用户省略扩展名,对话框是否自动在文件名中添加扩展名。
DefaultExt
设置文件默认扩展名
CreatePrompt
指示如果用户指定不存在的文件,对话框是否提示用户允许创建该文件
OverwritePrompt
指示如果用户指定的文件名已存在,Save As 对话框是否显示警告。

FontDialog

常用属性
AllowVectorFonts
指示是否可在字体列表中选择矢量字体,默认为true
AllowVerticalFonts
指示是否可在字体列表中选择垂直字体,默认为true
FixedPitchOnly
该属性设置为true,仅在字体列表中显示固定大小的字体,默认为false
MaxSize
定义用户可以选择的最大字号
MinSize
定义用户可以选择的最小字号
ShowApply
该属性设置为true,显示Apply按钮
ShowEffects
该属性设置为true,可以选择Strikeout和Underline复选框来处理字体
ShowColor
该属性设置为true,在对话框中显示Color选项
AllowScriptChange
该属性设置为false,可防止用户改变字体的脚本
FontMustExist
指示对话框是否指定当用户试图选择不存在的字体或样式时的错误条件
Apply事件
单击Apply按钮触发的事件

ColorDialog

2、掌握事件处理过程

不想掌握

第五章 流

1、 FileStream

  • 表示在磁盘或网络路径上指向文件的流,提供了在文件中读写字节的方法
  • 构造方法

public FileStream (string path, FileMode mode)
public FileStream(string path, FileMode mode, FileAccess access)

public override long Seek(long offset, SeekOrigin origin)
//offset为文件指针以字节为单位的移动距离
//origin为开始计算的起始位置
//SeekOrigin.Begin Current End
public override int Read(byte[] array, int offset, int count)
//array为字节数组
//offset为字节数组中开始写入数据的位置
//count指定从文件中读出多少字节
public override void Write(byte[] array, int offset, int count)
//使用从缓冲区读取的数据将字节块写入该流
byte[] byData = new byte[200];
char[] charData = new char[200];
try
{
    FileStream aFile = new FileStream("E:/程序/f2.java",
                                             FileMode.Open);
    aFile.Seek(90, SeekOrigin.Begin);
    aFile.Read(byData, 0, 200);
}

2、 StreamWriter与StreamReader

  • StreamWriter的构造方法

public StreamWriter (string path)
public StreamWriter (string path,bool append)
(append 为false,则创建一个新文件,或截取现有文件并打开它)
(append 为true,则打开文件,保留原有数据。如果找不到文件,则创建一个新文件)
public StreamWriter (Stream stream)

FileStream aFile = new FileStream(“Log.txt”, FileMode.CreateNew);
StreamWriter sw = new StreamWriter(aFile);
  • StreamWriter的Write和WriteLine方法
public override void Write(string value)
//将字符串写入流,参数除字符串外可以是任何基本数据类型
public virtual void WriteLine(string value)
//将字符串写入流并换行,参数除字符串外可以是任何基本数据类型
try
{
    FileStream aFile = new FileStream("E:/Log.txt“,    

                                     FileMode.OpenOrCreate);
    StreamWriter sw = new StreamWriter(aFile);
    bool truth = true;
    sw.WriteLine("Hello to you.");
    sw.WriteLine("It is now {0} and things are looking  

     good.",  DateTime.Now.ToLongDateString());

    sw.Write("More than that,");
    sw.Write(" it's {0} that C# is fun.", truth);
    sw.Close();
}

在这里插入图片描述

  • StreamReader的构造方法

public StreamReader (Stream stream)
public StreamReader (string path)

  • StreamReader的Read、ReadLine、ReadToEnd方法
public override int Read()
//读取输入流中的下一个字符。到达流末尾则返回 - 1
public override int Read(char[] buffer, int index, int count)
//从index开始,将当前流中的最多count个字符读入buffer中
public override string ReadLine()
//从当前流中读取一行字符,并将数据作为字符串返回
public override string ReadToEnd()
//从流的当前位置到末尾读取流

3、 BinaryReader和BinaryWriter

BinaryReader
常用函数
public virtual XXX readXXX() //对各种基本数据类型的读取
public virtual int Read()
public virtual int Read(byte[] buffer, int index, int count)
public virtual int Read(char[] buffer, int index, int count)
构造函数
public BinaryReader (Stream input)
public BinaryReader(Stream input, Encoding encoding)
BinaryWriter
常用函数
public virtual void Write(XXX xx)
public virtual void Write(byte[] buffer, int index, int count)
public virtual void Write(char[] chars, int index, int count)
构造函数
protected BinaryWriter ()
public BinaryWriter (Stream output)
public BinaryWriter (Stream output, Encoding encoding)

4、 对象的串行化

  • .NETFramework在System.Runtime.Serialization和System.Runtime.Serialization.Formatters命名空间中提供了串行化对象的基础架构
  • System.Runtime.Serialization.Formatters.Binary在该命名空间中包含的BinaryFormatter类能把对象串行化为二进制数据,把二进制数据串行化为对象
  • System.Runtime.Serialization.Formatters.Soap在该命名空间中包含的SoupFormatter类能把对象串行化为SOAP格式的XML数据,把SOAP格式的XML数据串行化为对象

IFormatter接口
void Serialize(Stream serializationStream, Object graph)
将对象或具有给定根的对象图形序列化为所提供的流。
Object Deserialize(Stream serializationStream)
反序列化所提供流中的数据并重新组成对象图形

IFormatter serializer = new BinaryFormatter();
FileStream saveFile = new FileStream("E:/Products.bin",
                                       FileMode.Create, FileAccess.Write);
serializer.Serialize(saveFile, products);
saveFile.Close();

FileStream loadFile = new FileStream("E:/Products.bin",
                                       FileMode.Open, FileAccess.Read);
List<Product> savedProducts =
                         serializer.Deserialize(loadFile) as List<Product>; 
loadFile.Close();

5、监控文件结构FileSystemWatcher

在这里插入图片描述

启用FileSystemWatcher对象之前必须设置的属性
Path
设置要监控的文件位置或目录
NotifyFilter
NotifyFilters枚举值规定在被监控的文件内要监控哪些内容
Filter
监控文件的过滤器

必须为Changed、Created、Deleted和Renamed编写事件处理程序
当修改与Path、NotifyFilter和Filter属性匹配的文件或目录时,就引发每一个事件
将EnableRaisingEvents属性设置为true,就可以开始监控工作

private FileSystemWatcher watcher;
private delegate void UpdateWatchTextDelegate(string newText);

public Form1()
{
    InitializeComponent();
    this.watcher = new System.IO.FileSystemWatcher();
    this.watcher.Deleted +=
       new System.IO.FileSystemEventHandler(this.OnDelete);
    this.watcher.Renamed +=
       new System.IO.RenamedEventHandler(this.OnRenamed);
    this.watcher.Changed +=
       new System.IO.FileSystemEventHandler(this.OnChanged);
    this.watcher.Created +=
       new System.IO.FileSystemEventHandler(this.OnCreate);
    DirectoryInfo aDir = new DirectoryInfo(@"C:\\FileLogs");
    if (!aDir.Exists)
        aDir.Create();
}
private void cmdWatch_Click(object sender, EventArgs e)//设置属性
{
    watcher.Path = Path.GetDirectoryName(txtLocation.Text);
    watcher.Filter = Path.GetFileName(txtLocation.Text);
    watcher.NotifyFilter = NotifyFilters.LastWrite |
       NotifyFilters.FileName | NotifyFilters.Size;
    lblWatch.Text = "Watching " + txtLocation.Text;
    watcher.EnableRaisingEvents = true;
}
public void OnChanged(object source, FileSystemEventArgs e) { }
public void OnRenamed(object source, RenamedEventArgs e) { }
public void OnDelete(object source, FileSystemEventArgs e) { }
public void OnCreate(object source, FileSystemEventArgs e) { }

第六章 多线程技术

1、 线程的建立与启动

//public Thread (ThreadStart start)指定入口
Thread compressThread = new Thread(entryPoint);
static void DoCompress(){
	//代码
}
ThreadStart entryPoint = new ThreadStart(DoCompress);

2、 线程的常用方法,Start、Join、Sleep。

//启动线程,即给线程分配除处理器之外的系统资源并执行各种安全性检查。
compressThread.Start();//在调用该方法后,新线程并不是处于Running状态,而是处于Unstarted状态

compressThread.Suspend();//使线程处于阻塞状态
compressThread.Resume();//恢复被挂起的线程,进入就绪状态
compressThread.Abort();//中止线程,永久删除不能重新启动
compressThread.Join();//进入阻塞,等待调用该方法线程执行完毕
compressThread.Sleep(int millisecondsTimeout);//睡会儿,睡醒就绪

3、 Thread类的常用属性 CurrentThread、Name、Priority

CurrentThread
获取当前正在运行的线程
Thread myOwnThread = Thread.CurrentThread;

Name
线程的名称

Priority
指示线程的调度优先级
从高到低为:(同级时间片轮转)
ThreadPriority.Highest
ThreadPriority.AboveNormal
ThreadPriority.Normal
ThreadPriority.BelowNormal
ThreadPriority.Lowest

4、 给线程传递数据

Thread类有一个带参数的委托类型的重载形式。这个委托的定义如下:

[ComVisibleAttribute(false)]
public delegate void ParameterizedThreadStart(Object obj)

这个Thread类的构造方法的定义如下:

public Thread(ParameterizedThreadStart start);

下面的代码使用了这个带参数的委托向线程传递一个字符串参数:

public static void myStaticParamThreadMethod(Object obj)  
{  
	Console.WriteLine(obj);  
}  
 
static void Main(string[] args)  
{  
	Thread thread = new Thread(myStaticParamThreadMethod);  
	thread.Start("通过委托的参数传值");  
}  

要注意的是,如果使用的是不带参数的委托,不能使用带参数的Start方法运行线程,否则系统会抛出异常。但使用带参数的委托,可以使用thread.Start()来运行线程,这时所传递的参数值为null。

也可以定义一个类来传递参数值,如下面的代码如下

class MyData  
{  
	private String d1;  
	private int d2;  
	public MyData(String d1, int d2)  
	{  
		this.d1 = d1;  
		this.d2 = d2;  
	}  
	public void threadMethod()  
	{  
		Console.WriteLine(d1);  
		Console.WriteLine(d2);  
	}  
}  
 
MyData myData = new MyData("abcd",1234);  
Thread thread = new Thread(myData.threadMethod);  
thread.Start();  

如果使用定义的MyThread类,传递参数会显示更简单,代码如下:

class NewThread : MyThread  
{  
    private String p1;  
    private int p2;  
    public NewThread(String p1, int p2)  
    {  
        this.p1 = p1;  
        this.p2 = p2;  
    }  
 
    override public void run()  
    {  
        Console.WriteLine(p1);  
        Console.WriteLine(p2);  
    }  
}  
 
NewThread newThread = new NewThread("hello world", 4321);  
newThread.start();  

5、后台线程

在这里插入图片描述

public static void myStaticThreadMethod()  
{  
    Thread.Sleep(3000);  
}  
 
Thread thread = new Thread(myStaticThreadMethod);  
// thread.IsBackground = true;  
thread.Start();  
  • 如果运行上面的代码,程序会等待3秒后退出,如果将注释去掉,将thread设成后台线程,则程序会立即退出。
  • 要注意的是,必须在调用Start方法之前设置线程的类型,否则一但线程运行,将无法改变其类型。

6、线程池

ThreadPool

  • 线程池是可以用来在后台执行多个任务的线程集合,使主线程可以自由地异步执行其它任务
  • 该类会在需要时增减池中线程的个数,直到最大的线程数。
  • 池中的最大线程数是可配置的
  • 如果有更多的工作要处理,而线程池中线程的使用也到了极限,最新的工作就要排队,必须等待线程完成其任务
  • 线程池中的每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中
  • 如果线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间之后创建另一个辅助线程
  • 线程池适合于执行一些需要多个线程的任务。使用线程池可以优化任务的执行过程,提高吞吐量;如果应用程序需要对线程进行特定的控制,则不适合使用线程池,需要创建并管理自己的线程
  • 每个进程都有且仅有一个线程池。当进程启动时,线程池不会自动创建。只有第一次将回调方法排入队列时才会创建线程池。
  • 线程池通常用于服务器应用程序,每个传入请求都将分配给线程池中的一个线程,因此可以异步处理请求,而不会占用主线程,也不会延迟后续请求的处理

ThreadPool常用方法
void GetMaxThreads(out int workerThreads,out int completionPortThreads)
//检索可以同时处于活动状态的线程池请求的数目,所有大于此数目的请求将保持排队状态,直到线程池线程变为可用
-bool QueueUserWorkItem ( WaitCallback callback )
-public delegate void WaitCallback(Object state)
将方法排入队列以便执行,此方法在有线程池线程变得可用时执行 。线程池收到请求后会从池中选择一个线程来调用该方法。如果线程池还没有运行,就会创建一个线程池,并启动第一个线程。如果线程池已经在运行,且有一个空闲线程来完成该任务就把该作业传递给这个线程

using System;
using System.Threading;

namespace Wrox.ProCSharp.Threading
{
    class Program
    {
        static void Main()
        {
            int nWorkerThreads;
            int nCompletionPortThreads;
            ThreadPool.GetMaxThreads(out nWorkerThreads, out nCompletionPortThreads);
            Console.WriteLine("Max worker threads: {0}, I/O completion threads: {1}", nWorkerThreads, nCompletionPortThreads);

            for (int i = 0; i < 7; i++)
            {
                ThreadPool.QueueUserWorkItem(JobForAThread);

            }

            Thread.Sleep(30000);
            Console.ReadLine();
        }


        static void JobForAThread(object state)
        {
            for (int i = 0; i < 4; i++)
            {
                Console.WriteLine("loop {0}, running inside pooled thread {1}", i,
                   Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(50);
            }

        }
    }

}

7、线程同步

  • (1)System.Threading.Monitor类提供的静态方法 Enter、Exit、Wait、Pulse、PulseAll
public static void Enter ( Object obj ) 
//在指定对象上获取排它锁
public static void Exit ( Object obj )  
//释放指定对象上的排它锁
public static bool Wait ( Object obj )
//释放对象上的锁并阻塞当前线程,直到它重新获取该锁。 
public static void Pulse ( Object obj ) 
//通知等待队列中的线程锁定对象状态的更改。
public static void PulseAll ( Object obj ) 
//通知所有的等待线程对象状态的更改
  • (2)线程同步的几种方法:

1使用Lock(锁定对象) { 互斥代码块 } 实现互斥

//C#的lock关键字提供了与Monitor.Enter和Monitor.Exit同样的功能
lock  ( obj )
{
    ...
}

2设Mutex类对象为mut,在互斥代码块前使用mut.WaitOne( ),在互斥代码块后使用mut.ReleaseMutex( )

典型的使用Mutex同步需要完成三个步骤的操作:1.打开或者创建一个Mutex实例;2.调用WaitOne()来请求互斥对象;3.最后调用ReleaseMutex来释放互斥对象。

if ( Mut.WaitOne() )
{
     try
    {
        ……
     }
     finally
    {
        mut.ReleaseMutex( );
    }
}
else
{
      ……
}

3在互斥代码块前使用Monitor.Enter(锁定对象),在互斥代码块后使用Monitor.Exit(锁定对象)

Monitor.Enter(obj);
try
{
……
}
finally
{
Monitor.Exit(obj);
}

4使用Interlocked
在这里插入图片描述
在这里插入图片描述
5等待句柄
在这里插入图片描述

6使用Semphore
在这里插入图片描述

static void Main()
{
	int threadCount = 6;
	int semaphoreCount = 4;
	var semaphore = new SemaphoreSlim(semaphoreCount, semaphoreCount);
	var threads = new Thread[threadCount];
	for (int i = 0; i < threadCount; i++)
	{
		threads[i] = new Thread(ThreadMain);
		threads[i].Start(semaphore);
	}
	for (int i = 0; i < threadCount; i++)
	{
		threads[i].Join();
	}
	Console.WriteLine("All threads finished");
	Console.ReadLine();
}

static void ThreadMain(object o)
{
	SemaphoreSlim semaphore = o as SemaphoreSlim;
	Trace.Assert(semaphore != null, "o must be a Semaphore type");
	bool isCompleted = false;
	while (!isCompleted)
	{
		if (semaphore.Wait(600))
		{
			try
			{
				Console.WriteLine("Thread {0} locks the semaphore",
				Thread.CurrentThread.ManagedThreadId);
			T	hread.Sleep(2000);
			}
			finally
			{
				semaphore.Release();
				Console.WriteLine("Thread {0} releases the semaphore",
				Thread.CurrentThread.ManagedThreadId);
				isCompleted = true;
			}
		}
		else
		{
			Console.WriteLine("Timeout for thread {0}; wait again",
			Thread.CurrentThread.ManagedThreadId);
		}
}

7 ReaderWriterLockSlim
在这里插入图片描述

try
{
	while (!rwl.TryEnterWriteLock(50))
	{
		Console.WriteLine("Writer {0} waiting for the write lock",writer);
		Console.WriteLine("current reader count: {0}",rwl.CurrentReadCount);
	}
	Console.WriteLine("Writer {0} acquired the lock", writer);
	for (int i = 0; i < items.Count; i++)
	{
		items[i]++;
		Thread.Sleep(50);
	}
	Console.WriteLine("Writer {0} finished", writer);
}
finally
{
	rwl.ExitWriteLock();
}

8、任务Task

  • 创建Task对象
class Program
{
    static void DownLoad(object str)
    {
        Console.WriteLine("DownLoad Begin ID = " + Thread.CurrentThread.ManagedThreadId + " " + str);
        Thread.Sleep(1000);
        Console.WriteLine("DownLoad End");
    }
    static void Main(string[] args)
    {
        //创建任务
        Task task = new Task(DownLoad, "人民日报");
        //启动任务
        task.Start();
        Console.WriteLine("Main");   
        Console.ReadKey();
    }
}
  • 任务工厂TaskFactory
class Program
{
    static void DownLoad(object str)
    {
        Console.WriteLine("DownLoad Begin ID = " + Thread.CurrentThread.ManagedThreadId + " " + str);
        Thread.Sleep(1000);
        Console.WriteLine("DownLoad End");
    }
    static void Main(string[] args)
    {
        //创建任务工厂
        TaskFactory taskFactory = new TaskFactory();
        //开始新的任务
        taskFactory.StartNew(DownLoad, "纽约时报");
        Console.WriteLine("Main");
        Console.ReadKey();
    }
}
  • 连续任务

如果一个任务的执行依赖于另一个任务,即任务的执行有先后顺序。此时,我们可以使用连续任务。
task.ContinueWith(ReadNews)表示一个任务task结束后,才开始执行另一个任务。

class Program
{
    static void DownLoad(object str)
    {
        Console.WriteLine("DownLoad Begin ID = " + Thread.CurrentThread.ManagedThreadId + " " + str);
        Thread.Sleep(1000);
        Console.WriteLine("DownLoad End");
    }
    static void ReadNews(Task obj)
    {
        Thread.Sleep(1000);
        Console.WriteLine("Read News");
    }
    static void Main(string[] args)
    {
        Task task = new Task(DownLoad, "人民日报");
        Task task2 = task.ContinueWith(ReadNews);
        task.Start();
        Console.ReadKey();
    }
}
  • 任务的层次结构

在一个任务中可以启动子任务,两个任务异步执行。默认情况下,子任务(即由外部任务创建的内部任务)将独立于其父任务执行。
使用TaskCreationOptions.AttachedToParent显式指定将任务附加到任务层次结构中的某个父级。
如果父任务执行完了但是子任务没有执行完,则父任务的状态会被设置为WaitingForChildrenToComplete,只有子任务也执行完了,父任务的状态才会变成RunToCompletion。

class Program
{
    static void DownLoad(object str)
    {
        Console.WriteLine("Parent Begin ID = " + Thread.CurrentThread.ManagedThreadId + " " + str);
 
        Task child = new Task(ChildWork, TaskCreationOptions.AttachedToParent);
        child.Start();
        Thread.Sleep(1000);
        Console.WriteLine("Parent End");
    }
 
    static void ChildWork()
    {
        Console.WriteLine("Child begin");
        Thread.Sleep(5000);
        Console.WriteLine("Child end");
    }
 
    static void Main(string[] args)
    {
        Task task = new Task(DownLoad, "人民日报");
        task.Start();
        Thread.Sleep(2000);
        Console.WriteLine(task.Status);
        Thread.Sleep(4000);
        Console.WriteLine(task.Status);
        Console.ReadKey();
    }
}
  • 任务的执行结果

使用Task的泛型版本,可以返回任务的执行结果。
下面例子中的TaskWithResult的输入为object类型,返回一个元组Tuple<int, int>。
定义调用TaskWithResult的任务时,使用泛型类Task<Tuple<int, int>>,泛型的参数定义了返回类型。通过构造函数,传递TaskWithResult,构造函数的第二个参数定义了TaskWithResult的输入值。
任务完成后,通过Result属性获取任务的结果。

class Program
{
    static Tuple<int, int> TaskWithResult(object obj)
    {
        Tuple<int, int> div = (Tuple<int, int>)obj;
        Thread.Sleep(1000);
        return Tuple.Create<int, int>(div.Item1 + div.Item2, div.Item1 - div.Item2);
    }
    static void Main(string[] args)
    {
        var task = new Task<Tuple<int, int>>(TaskWithResult, Tuple.Create<int, int>(8, 3));            
        task.Start();
        Console.WriteLine(task.Result);
        task.Wait();
        Console.WriteLine("Result: {0} {1}", task.Result.Item1, task.Result.Item2);
        Console.ReadLine();
    }
}

9、死锁的产生和避免

在这里插入图片描述在这里插入图片描述

10、异步委托

首先我们来看一个简单的例子:

小明在烧水,等水烧开以后,将开水灌入热水瓶,然后开始整理家务
小文在烧水,在烧水的过程中整理家务,等水烧开以后,放下手中的家务活,将开水灌入热水瓶,然后继续整理家务
这也是日常生活中很常见的情形,小文的办事效率明显要高于小明。从C#程序执行的角度考虑,小明使用的同步处理方式,而小文则使用的异步处理方式。
同步处理方式下,事务是按顺序一件一件处理的;而异步方式则是,将子操作从主操作中分离出来,主操作继续进行,子操作在完成处理的时候通知主操作。

【异步委托】
我们通过异步委托来完成:

class BoilWater
{
	static TimeSpan Boil()
	{
		Console.WriteLine("水壶:开始烧水...");
		Thread.Sleep(6000);
		Console.WriteLine("水壶:水已经烧开了!");
		return TimeSpan.MinValue;
	}
	delegate TimeSpan BoilingDelegate();
 
	static void Main(string[] args)
	{
		Console.WriteLine("小文:将水壶放在炉子上");
		BoilingDelegate d = new BoilingDelegate(Boil);
        IAsyncResult result = d.BeginInvoke(BoilingFinishedCallback, null);
		Console.WriteLine("小文:开始整理家务...");
        for (int i = 0; i < 20; i++)
        {
			Console.WriteLine("小文:整理第{0}项家务...", i + 1);
			Thread.Sleep(1000);
        }
	}
 
	static void BoilingFinishedCallback(IAsyncResult result)
	{
		AsyncResult asyncResult = (AsyncResult)result;
		BoilingDelegate del = (BoilingDelegate)asyncResult.AsyncDelegate;
        del.EndInvoke(result);
        Console.WriteLine("小文:将热水灌到热水瓶");
        Console.WriteLine("小文:继续整理家务");
	}   
}

11、屏障Barrier

C# 并行编程 之 Barrier的使用 - 通过屏障同步并发任务

第七章 数据库应用开发

1、 ADO.NET的主要特点

  • 不依赖于连续的活动连接
  • 使用数据命令执行数据库交互
  • 使用数据集缓存数据
  • 数据集独立于数据源
  • 数据保持为XML
  • 通过架构定义数据结构

2、 ADO.NET对象模型的结构 DataAdapter、Command、DataReader、DataSet

在这里插入图片描述
在这里插入图片描述

3、 连接对象的创建

String connectStr = "server=自己的Server名称;database=库名;uid=数据库账号;pwd=数据库密码"; //SQL server身份验证方法
SqlConnection connect = new SqlConnection(connectStr);

4、 执行数据命令的方法(ExecuteNonQuery、ExecuteReader、ExecuteScalar)

在这里插入图片描述

5、 如何使用SqlCommand和SqlDataReader读取SQL Server数据库表,如何使用SqlCommand添加、删除和修改SQL Server数据库表

String connStr = "server=自己的Server名称;database=库名;uid=数据库账号;pwd=数据库密码"; //SQL server身份验证方法
SqlConnection conn = new SqlConnection(connStr);
//打开链接
conn.Open();

#region 查询
//创建命令
MySqlCommand cmd = new MySqlCommand("select * from user ", conn);
//执行命令--读取数据
MySqlDataReader  reader= cmd.ExecuteReader();
//读取数据
while(reader.Read())//判断是否有数据--//读取一行记录
{

    string username= reader.GetString("username");
    string password = reader.GetString("password");
    Console.WriteLine(username + ":" + password);
}
reader.Close();
#endregion


#region 插入数据            
string username = "cwer";
string password = "lcker"; 
//创建语句
MySqlCommand cmd = new MySqlCommand("insert into user set username='" + username + "'" + ",password='"+password+"'",conn);
//执行语句
cmd.ExecuteNonQuery();
conn.Close();
Console.ReadKey();//程序暂停
#endregion

6、 DataSet的主要属性(Tables、Relations)

在这里插入图片描述

7、 DataTable的主要属性(Columns、Rows、PrimaryKey)

在这里插入图片描述

8、 如何使用SqlDataAdapter对象把SQL Server数据库表填充到DataSet的DataTable中,如何修改、添加、删除DataTable中的记录并更新到数据库中

使用SqlDataAdapter对象把SQL Server数据库表填充到DataSet的DataTable中

SqlConnection thisConnection = new SqlConnection(connStr);
SqlDataAdapter thisAdapter = new SqlDataAdapter("SELECT * FROM student", thisConnection);
DataSet thisDataSet = new DataSet();	
thisAdapter.Fill(thisDataSet, "MyStudent");//Fill
foreach (DataRow theRow in thisDataSet.Tables["MyStudent"].Rows)
{
    Console.WriteLine(theRow["姓名"] + "\t" + theRow["年龄"]);
}
thisConnection.Close();

修改、添加、删除DataTable中的记录并更新到数据库中

SqlConnection thisConnection = new SqlConnection(connstr);
SqlDataAdapter thisAdapter = new SqlDataAdapter("SELECT * FROM student", thisConnection);
SqlCommandBuilder thisBuilder =  new SqlCommandBuilder(thisAdapter);//负责生成用于更新数据库的SQL语句,不必自己创建这些语句
DataSet thisDataSet = new DataSet();
thisAdapter.Fill(thisDataSet, "Stu");
//修改
thisDataSet.Tables["Stu"].Rows[0]["年龄"] = 18;
thisAdapter.Update(thisDataSet, "Stu");//遍历DataTable中的行,找出需要对数据库作出的变动
//添加
DataRow thisRow = thisDataSet.Tables["Stu"].NewRow();
thisRow["姓名"] = "张华";
thisRow["年龄"] = 21;
thisDataSet.Tables["Stu"].Rows.Add(thisRow);
thisAdapter.Update(thisDataSet, "Stu");
//删除
DataColumn[] keys = new DataColumn[1];
keys[0] = thisDataSet.Tables["Stu"].Columns["姓名"];
thisDataSet.Tables["Stu"].PrimaryKey = keys;
DataRow findRow = thisDataSet.Tables["Stu"].Rows.Find("张华");
if (findRow != null) {
     Console.WriteLine("张华 already in Customers table");
     Console.WriteLine("Removing 张华  . . .");
     findRow.Delete();
     thisAdapter.Update(thisDataSet, "Stu");
}

10、如何使用SqlCommand执行带输出参数的存储过程

create  procedure  RegionSelect  as
   set  nocount  off
   select * from Region
Go

private  static  SqlCommand  GenerateSelectCommand(SqlConnection   conn)
{
    SqlCommand  aCommand=new SqlCommand(“RegionSelect”,conn);
    aCommand.CommandType=CommandTye.StoredProcedure;
    aCommand.UpdatedRowSource=UpdateRowSource.None;
    return  aCommand;
 }
DataSet   ds=new   DataSet();
SqlDataAdapter  da=new  SqlDataAdapter();
da.SelectCommand=GenerateSelectCommand(conn);
da.Fill(ds,”Region”);

11、DataRowState和AcceptChanges、Update方法

DataRow类的RowState属性
DataRowState.Added 已添加
DataRowState.Deleted 已删除
DataRowState.Detached 已创建
DataRowState.Modified 已修改
DataRowState.Unchanged 未更改

一般在成功更新数据源之后调用AcceptChanges()方法

调用DataAdapter对象的Update( )方法,把这个变化返回到数据库中
thisAdapter.Update(thisDataSet, “Stu”);

12、在ADO.NET中访问多个表 见课堂示例程序TableRelation

没讲

13、DataColumnVersion

在这里插入图片描述

14、UpdatedRowSource的含义和示例

在这里插入图片描述

Both
存储过程可以返回输出参数和一个完整的数据库记录。这两个数据源都用于更新源数据行
FirstReturnedRecord
该命令返回一个记录,该记录的内容应合并到最初的源DataRow中,当给定的表有许多默认(或计算)列时,使用这个值很有用,因为在执行insert语句之后,这些行需要与客户端上的DataRow同步。
None
丢弃从该命令返回的所有数据
OutputParameters
命令的任何输出参数都映射到DataRow的对应列上

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

namespace UpdatedRowSourceDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            string ss = "Data Source=.\\MYSQLSERVER;Initial Catalog=MyDB;Integrated Security=True";
            SqlConnection con = new SqlConnection(ss);
            con.Open();
            SqlDataAdapter sda = new SqlDataAdapter();
            sda.SelectCommand =new SqlCommand( "select * from Student",con);
            SqlCommand cmd = new SqlCommand("ReturnValue5", con);
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Parameters.Add(new SqlParameter("rv", SqlDbType.Int, 10, ParameterDirection.Output, false, 10, 0, "年龄", DataRowVersion.Default, null));
            cmd.UpdatedRowSource = UpdateRowSource.Both;
            DataSet ds = new DataSet();
            sda.Fill(ds, "stu");
            sda.InsertCommand = cmd;
            DataRow dr = ds.Tables["stu"].NewRow();
            dr[0] = "蒋晓欣";
            dr[1] = 22;
            dr[2] = "女";
            dr[3] = "本科生";
            ds.Tables["stu"].Rows.Add(dr);
            sda.Update(ds, "stu");
         /*   sda.UpdateCommand = cmd;   */         
            DataTable dt = ds.Tables["stu"];
         /*   dt.Rows[0][1] = 40;
            sda.Update(ds, "stu");*/
            for (int i = 0; i < dt.Rows.Count; i++)
            {
                for (int j = 0; j < dt.Columns.Count; j++)
                    Console.Write(dt.Rows[i][j] + "  ");
                Console.WriteLine();
            } 
            con.Close();
        }
    }
}

第八章 泛型

1、.net 泛型与C++模板的不同

在这里插入图片描述

2、泛型在性能、类型安全、二进制代码的重用、代码的扩展方面的表现

  • 性能
    不再进行装箱和拆箱操作
  • 类型安全
    添加的类型得相同
  • 二进制代码的重用
    泛型类可以定义一次,并且可以用许多不同的类型实例化,而不需要像C++模板那样访问源代码
  • 代码的扩展

3、泛型类型的命名约定

在这里插入图片描述

public class List { }
public class LinkedList { }

4、创建泛型类

public class LinkedListNode<T>
{
	public LinkedListNode(T value)
	{
		this.Value = value;
	}
   
	public T Value { get; private set; }
	public LinkedListNode<T> Next { get; internal set; }
	public LinkedListNode<T> Prev { get; internal set; }
}

5、泛型类的功能

在这里插入图片描述

using System;
using System.Collections.Generic;
namespace Wrox.ProCSharp.Generics
{
    public class DocumentManager<T>        
    {
        private readonly Queue<T> documentQueue = new Queue<T>();
        public void AddDocument(T doc)
        {
            lock (this)
            {
                documentQueue.Enqueue(doc);
            }
        }
        public bool IsDocumentAvailable
        {
            get { return documentQueue.Count > 0; }
        }

6、泛型类型使用时需注意的地方

不能假定类提供了什么类型

Class  MyGenericClass<T1,T2,T3>
{
     private  T1  innerT1Object;
     public  MyGenericClass(T1  item)
     {  innerT1Object=new  T1();  }//编译错误
     public   T1  InnerT1Object
     {    
           get
           {   return  innerT1Object; }
      }
}

我们不知道T1是什么,也就不能使用它的构造函数,它甚至
可能没有构造函数,或者没有可公共访问的默认构造函数

当比较为泛型类型提供的类型值和null时,不能使用运算符==和!=

public  bool  Compare(T1  op1, T1  op2)
{
    if  (op1 != null && op2 != null)
           return  true;
   else
           return  false;
}

其中,如果T1是一个值类型,则总是假定它是非空的。于是,
在上面的代码中,Compare总是返回true。

7、默认值

在这里插入图片描述

8、约束

class  MyGenericClass<T1,T2>: MyBaseClass, IMyInterface
where T1:constraint1  where  T2: constraint2
{
     ……
}

在这里插入图片描述

只能为默认构造函数定义构造函数约束,不能为其他构造函数定义构造函数约束
使用泛型类型还可以合并多个约束

public  class  MyClass<T> where T: IFoo, new()
{
    ……
}
//如果使用new()作约束,它就必须是为类型指定的最后一个约束
class  MyGenericClass<T1,T2> where  T2: T1
{
     ……
}
//其中,T2必须与T1类型相同,或者继承自T1,这称为裸类型约束,
//表示一个泛型类型参数用作另一个类型参数的约束

9、继承

泛型类型可以实现泛型接口,也可以派生自一个类
泛型类可以派生自泛型基类

public  class  LinkedList<T> : IEnumerable<out T>
{
    ……
}

public  class  Base<T>{……}
public  class  Derived<T>: Base<T>{……}

10、静态成员

泛型类的静态成员只能在类的一个实例中共享

public    class   StaticDemo<T>
{
    public  static  int  x;
}

StaticDemo<string>.x=4;
StaticDemo<int>.x=5;
Console.WriteLine(StaticDemo<string>.x); //输出4

11、泛型运算符

//隐式转换
public static implicit operator List<Animal>(Farm<T> farm)
{
    List<Animal> result = new List<Animal>();
    foreach (T animal in farm)
    {
        result.Add(animal);
    }
    return result;
}

//重载+
public static Farm<T> operator+(Farm<T> farm1, List<T> farm2)
{
    Farm<T> result = new Farm<T>();
    foreach (T animal in farm1)
    result.Animals.Add(animal);
    foreach (T animal in farm2)
    {
        if (!result.Animals.Contains(animal))
            result.Animals.Add(animal);
    }
    return result;
}
public static Farm<T> operator +(List<T> farm1, Farm<T> farm2)
{
    return farm2 + farm1;
}

12、泛型接口的协变与抗变

在这里插入图片描述

public interface IIndex<out T>
{
    T this [int index] { get; }
    int Count { get; }         
}
IIndex<Rectangle> r=new RectangleCollection();
IIndex<Shape> s=r;

在这里插入图片描述

public interface IDisplay<in T>
{
    void Show(T item);
}

13、可空类型

Nullable<int> x;//1
int? x2;//2

14、泛型方法

void  Swap<T>(ref  T  x,  ref  T  y)
{
    T  temp;
    temp=x;
    x=y;
    y=temp;
}
int   i=4, j=5;
Swap<int>(ref  i, ref  j);

如果类是泛型的,就必须为泛型方法类型使用不同的标识符

15、泛型方法的规范

泛型方法可以重载,为特定的类型定义规范

public class MethodOverloads  {
    public void Foo<T>(T obj)   {
        Console.WriteLine("Foo<T>(T obj), obj type: {0}", obj.GetType().Name);
    }
    public void Foo(int x)  {
        Console.WriteLine("Foo(int x)");
    }
    public void Bar<T>(T obj)  {
        Foo(obj);
}
  • 在编译期间,会使用最佳匹配。如果传递了一个int,就选择带int参数的方法;对于其他参数类型,编译器会选择方法的泛型版本

  • 所调用的方法是在编译期间定义的,而不是运行期间

  • Bar()方法选择了泛型Foo()方法,而不是用int参数重载的Foo()方法,原因是编译器在编译期间选择Bar()方法调用的Foo()方法。由于Bar()方法定义了一个泛型参数,而且泛型Foo()方法匹配这个类型,所以调用了Foo()方法,在运行期间给Bar()传递一个int值不会改变这一点

第九章 C#语言的改进

1、自动属性

 public int MyProp{ get; set; }
  • 使用自动属性时,只能通过属性访问数据,不能通过底层的私有字段访问,因为我们不知道底层私有字段的名称
  • 自动属性必须包含get和set访问块,无法使用这种方式定义只读或只写属性

2、对象初始化器

<className>  <variableName>=new <className>
{
     <propertyOrField1>=<value1>,
     <propertyOrField2>=<value2>,
     ……
     <propertyOrFieldN>=<valueN>
 }

集合初始化器
在这里插入图片描述

3、类型推理

var varName = value;

  • 变量varName隐式地类型化为value的类型
  • 如果编译器不能确定var声明的变量类型,代码就不会编译。因此,在用var声明变量时,必须同时初始化该变量,因为如果没有初始值,编译器就不能确定变量的类型
    在这里插入图片描述
    var myArray=new[] {3,”not an int”,5};

4、Lambda表达式与匿名方法

匿名方法
匿名方法纯粹是为了委托目的而创建
delegate (parameters)
{
……
};

在这里插入图片描述

Lambda表达式的参数
隐式参数:(a,b)=>a+b
显式参数:(int a,int b)=>a+b

PerformOperations((a,b)=>a+b); 可改写为

PerformOperations(delegate(int a,int b)
{
	return a+b;
});

PerformOperations((a,b)=>
{
	return a+b;
}); 
评论 8 您还未登录,请先 登录 后发表或查看评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:创作都市 设计师:CSDN官方博客 返回首页

打赏作者

秃头给你一拳

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值