以下为本次实践代码:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace ConsoleTest { class Program { static void Main(string[] args) { //反射读取类私有属性 Person per = new Person("ismallboy", "20102100104"); Type t = per.GetType(); //获取私有方法 MethodInfo method = t.GetMethod("GetStuInfo", BindingFlags.NonPublic | BindingFlags.Instance); //访问无参数私有方法 string strTest = method.Invoke(per, null).ToString(); //访问有参数私有方法 MethodInfo method2 = t.GetMethod("GetValue", BindingFlags.NonPublic | BindingFlags.Instance); object[] par = new object[2]; par[0] = "ismallboy"; par[1] = 2; string strTest2 = method2.Invoke(per, par).ToString(); //获取私有字段 PropertyInfo field = t.GetProperty("Name", BindingFlags.NonPublic | BindingFlags.Instance); //访问私有字段值 string value = field.GetValue(per).ToString(); //设置私有字段值 field.SetValue(per, "new Name"); value = field.GetValue(per).ToString(); } } ////// 个人信息 /// class Person { private string Name { get; set; } private string StuNo { get; set; } public Person(string name, string stuNo) { this.Name = name; this.StuNo = stuNo; } private string GetStuInfo() { return this.Name; } private string GetValue(string str, int n) { return str + n.ToString(); } } }
如果使用dynamic的话,也可以如下:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace ConsoleTest { class Program { static void Main(string[] args) { //反射读取类私有属性 dynamic per = new Person("ismallboy", "20102100104"); Type t = per.GetType(); //获取私有方法 MethodInfo method = t.GetMethod("GetStuInfo", BindingFlags.NonPublic | BindingFlags.Instance); //访问无参数私有方法 string strTest = method.Invoke(per, null); //访问有参数私有方法 MethodInfo method2 = t.GetMethod("GetValue", BindingFlags.NonPublic | BindingFlags.Instance); object[] par = new object[2]; par[0] = "ismallboy"; par[1] = 2; string strTest2 = method2.Invoke(per, par); //获取私有字段 PropertyInfo field = t.GetProperty("Name", BindingFlags.NonPublic | BindingFlags.Instance); //访问私有字段值 string value = field.GetValue(per); //设置私有字段值 field.SetValue(per, "new Name"); value = field.GetValue(per); } } ////// 个人信息 /// class Person { private string Name { get; set; } private string StuNo { get; set; } public Person(string name, string stuNo) { this.Name = name; this.StuNo = stuNo; } private string GetStuInfo() { return this.Name; } private string GetValue(string str, int n) { return str + n.ToString(); } } }
JavaScript中一个重要的概念就是闭包,闭包在JavaScript中有大量的应用,但是你知道么?C#也可以创建Closure。下面就介绍一下如何在C#中创建神奇的闭包。
在这之前,我们必须先知道如何在C#中定义函数
//函数定义,参数为string,返回为string FuncmyFunc = delegate(string msg) { return "Msg:" + msg; };
利用Lambda表达式也可以简化上述的代码,但是效果一样:
//Lambda FuncmyFuncSame = msg => "Msg:" + msg;
定义好函数后,可以进行调用:
//函数调用 string message= myFuncSame("Hello world");
定义一个带外部变量(相对于内嵌函数而言)的嵌套函数,外部函数将内部嵌套的函数进行返回:
public static FuncFunc() { var myVar = 1; Func inc = delegate(int var1) { //myVar能够记录上一次调用后的状态(值) myVar = myVar + 1; return var1 + myVar; }; return inc; }
C# Closure调用如下所示:
static void CsharpClosures() { var inc = Func(); Console.WriteLine(inc(5));//7 Console.WriteLine(inc(6));//9 }
当第二次调用inc(6)时,此时函数内变量myVar并未像第一次调用函数时进行重新初始化(var myVar=1),而是保留了第一次运算的值,即 2,因此inc(6)返回的结果为(2+1+6)=9.
要求:密码必须包含数字和字母
思路:1.列出数字和字符。 组成字符串 :chars
2.利用randrom.Next(int i)返回一个小于所指定最大值的非负随机数。
3. 随机取不小于chars长度的随机数a,取字符串chars的第a位字符。
4.循环 8次,得到8位密码
5.循环N次,批量得到密码。
代码实现如下 Main函数:
static void Main(string[] args) { string chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; Random randrom = new Random((int)DateTime.Now.Ticks); string path1 = @"C:\Users\lenovo\Desktop\pws.txt"; for (int j = 0; j < 10000;j++ ) { string str = ""; for (int i = 0; i < 8; i++) { str += chars[randrom.Next(chars.Length)];//randrom.Next(int i)返回一个小于所指定最大值的非负随机数 } if (IsNumber(str))//判断是否全是数字 continue; if (IsLetter(str))//判断是否全是字母 continue; File.AppendAllText(path1, str); string pws = Md5(str,32);//MD5加密 File.AppendAllText(path1, "," + pws + "\r\n"); } Console.WriteLine("ok"); Console.Read(); }
巧用String.trim 函数,判断是否全是数字,全是字母。
说明:string.trim 从 String 对象移除前导空白字符和尾随空白字符。
返回:一个字符串副本,其中从该字符串的开头和末尾移除了所有空白字符。
有一个重载:string.Trim(params char[] trimChars)
//从当前System.string对象移除数组中指定的一组字符的所有前导匹配项和尾部匹配项
trimChars:要删除的字符数组
方法实现如下代码:
//判断是否全是数字 static bool IsNumber(string str) { if (str.Trim("0123456789".ToCharArray()) == "") return true; return false; } //判断是否全是字母 static bool IsLetter(string str) { if (str.Trim("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".ToCharArray()) == "") return true; return false; }
用MD5加密,算法代码实现如下:
////// MD5加密 /// /// 加密字元 /// 加密位数16/32 ///public static string Md5(string str, int code) { string strEncrypt = string.Empty; MD5 md5 = new MD5CryptoServiceProvider(); byte[] fromData = Encoding.GetEncoding("GB2312").GetBytes(str); byte[] targetData = md5.ComputeHash(fromData); for (int i = 0; i < targetData.Length; i++) { strEncrypt += targetData[i].ToString("X2"); } if (code == 16) { strEncrypt = strEncrypt.Substring(8, 16); } return strEncrypt; }
生成批量密码,和加密后的密码如下图:
1. foreach
C#编译器会把foreach语句转换为IEnumerable接口的方法和属性。
foreach (Person p in persons) { Console.WriteLine(p); }
foreach语句会解析为下面的代码段。
调用GetEnumerator()方法,获得数组的一个枚举
在while循环中,只要MoveNext()返回true,就一直循环下去
用Current属性访问数组中的元素
IEnumerator enumerator = persons. GetEnumerator(); while (enumerator.MoveNext()) { Person p = (Person) enumerator.Current; Console.WriteLine(p); }
2. yield语句
yield语句的两种形式:
yield return; yield break;
使用一个yield return语句返回集合的一个元素
包含yield语句的方法或属性是迭代器。迭代器必须满足以下要求
a. 返回类型必须是IEnumerable、IEnumerable
b. 它不能有任何ref或out参数
yield return语句不能位于try-catch快。yield return语句可以位于try-finally的try块
try { // ERROR: Cannot yield a value in the boday of a try block with a catch clause yield return "test"; } catch { } try { // yield return "test again"; } finally { } try { } finally { // ERROR: Cannot yield in the body of a finally clause yield return ""; }
yield break语句可以位于try块或catch块,但是不能位于finally块
下面的例子是用yield return语句实现一个简单集合的代码,以及用foreach语句迭代集合
using System; using System.Collections.Generic; namespace ConsoleApplication6 { class Program { static void Main(string[] args) { HelloCollection helloCollection = new HelloCollection(); foreach (string s in helloCollection) { Console.WriteLine(s); Console.ReadLine(); } } } public class HelloCollection { public IEnumeratorGetEnumerator() { // yield return语句返回集合的一个元素,并移动到下一个元素上;yield break可以停止迭代 yield return "Hello"; yield return "World"; } } }
使用yield return语句实现以不同方式迭代集合的类:
using System; using System.Collections.Generic; namespace ConsoleApplication8 { class Program { static void Main(string[] args) { MusicTitles titles = new MusicTitles(); foreach (string title in titles) { Console.WriteLine(title); } Console.WriteLine(); foreach (string title in titles.Reverse()) { Console.WriteLine(title); } Console.WriteLine(); foreach (string title in titles.Subset(2, 2)) { Console.WriteLine(title); Console.ReadLine(); } } } public class MusicTitles { string[] names = { "a", "b", "c", "d" }; public IEnumeratorGetEnumerator() { for (int i = 0; i < 4; i++) { yield return names[i]; } } public IEnumerable Reverse() { for (int i = 3; i >= 0; i--) { yield return names[i]; } } public IEnumerable Subset(int index, int length) { for (int i = index; i < index + length; i++) { yield return names[i]; } } } }
问题引出
这视乎是个完全不必要进行讨论的话题,因为linq(这里具体是linq to objects)本来就是针对集合类型的,数组类型作为集合类型的一种当然可以使用了。不过我还是想写一下,这个问题源于qq群里一位朋友的提问:.net的数组类型都隐式继承了Array类,该类是一个抽象类,并且实现了IEnumerable、ICollection、IList接口。但linq的方法都是针对实现了IEnumerable
linq to objects的本质是通过扩展方法来实现集合的查询,这些扩展方法定义在一个Enumerable的静态类中。Enumerable类下的所有扩展方法的第一个参数都是IEnumerable
浅析数组类型
1. 所有数组类型都隐式派生自Array
当我们定义一个FileStream[] 数组时,CLR会为当前的AppDomain创建一个FileStream[] 类型,该类型派生自 Array。所以数组是引用类型,在堆中分配内存空间。Array类是一个抽象类,定义了许多关于常用的实例方法和静态方法,供所有的数组类型使用。例如常见的:Length属性,CopyTo方法等等。
2. 所有的数组类型都隐式实现了IEnumerable
就如上面所所的,这是一个理所当然的问题,为了提高开发效率,数组类型理应可以使用linq进行查询。但由于数组可以是多维数组或者非0基数组,所以Array类并没有实现IEnumerable
由于CLR的隐式实现,才使我们可以将一维数组类型应用在需要IEnumerable
按照上面的说法,我们可以将FileStream[] 类型的对象传递给如下的方法:
void F1(IEnumerable
void F2(ICollection
void F3(IList
这是对于引用类型而言的,如果是值类型,则不为会它的基类实现这些接口。例如DateTimel类型(基类包括ValueType和Object),DateTime[]数组类型不能传递给上面的F1方法,这是因为值类型的数组的内存布局与引用类型的数组不同。
具名参数 和 可选参数 是 C# framework 4.0 出来的新特性。
一. 常规方法定义及调用
public void Demo1(string x, int y) { //do something... } public void Main() { //调用 Demo1("similar", 22); }
调用时,参数顺序(类型)必须与声明一致,且不可省略。
二. 可选参数的声明及调用
可选参数分为两种情况: 1. 部分参数可选; 2. 全部参数都是可选
//部分可选(x为必选,y为可选) public void Demo2(string x, int y = 5) { //do something... } public void Main() { //调用 Demo2("similar"); // y不传入实参时,y使用默认值5 Demo2("similar", 10); // y传入实参,则使用实参10 }
注: 当参数为部分可选时, 可选参数 的声明必须定义在 不可选参数 的后面(如上: y 的声明在 x 之后),不然会出现如下错误提示:
//全部可选(x,y 均为可选参数) public void Demo3(string x = "demo", int y = 5) { //do something... } public void Main() { //调用 Demo3(); // x,y不传入实参时,x,y使用默认值 "demo",5 Demo3("similar"); // y不传入实参时,y使用默认值5 Demo3("similar", 10); // x,y都传入实参 }
分先后。
b. 参数声明定义可以无顺序,但调用时必须与声明时的一致。
上面的调用只写的3种,其实还有一种,就是 x 使用默认值,y 传入实参,即 : Demo3(10);
但这样调用会报错,因为Demo3的第一个参数是 string 类型,错误消息如图:
但是现在我只想传入y, 不想传入 x ,该怎么办呢,那么就要用到 C#的 具名参数。
三. 具名参数
具名参数的使用主要是体现在函数调用的时候。
public void Main() { //调用 Demo3(); // x,y不传入实参时,x,y使用默认值 "demo",5 Demo3("similar"); // y不传入实参时,y使用默认值5 Demo3("similar", 10); // x,y都传入实参 // 具名参数的使用 Demo3(y:10); }
通过具名参数,我们可以指定特定参数的值,这里我们通过 Demo3(y:10)就可以解决我们上面遇到的问题(x使用默认值,y使用实参)。
注: 当使用 具名参数时,调用方法可以不用管参数的声明顺序,即如下调用方式也是可以的:
在调用含有可选参数的方法时,vs中会有智能提示,提示哪些是可以选参数及其默认值,中括号表示可选[]:
具名参数 和 可选参数 是 C# framework 4.0 出来的新特性。
一. 常规方法定义及调用
public void Demo1(string x, int y) { //do something... } public void Main() { //调用 Demo1("similar", 22); }
调用时,参数顺序(类型)必须与声明一致,且不可省略。
二. 可选参数的声明及调用
可选参数分为两种情况: 1. 部分参数可选; 2. 全部参数都是可选
//部分可选(x为必选,y为可选) public void Demo2(string x, int y = 5) { //do something... } public void Main() { //调用 Demo2("similar"); // y不传入实参时,y使用默认值5 Demo2("similar", 10); // y传入实参,则使用实参10 }
注: 当参数为部分可选时, 可选参数 的声明必须定义在 不可选参数 的后面(如上: y 的声明在 x 之后),不然会出现如下错误提示:
//全部可选(x,y 均为可选参数) public void Demo3(string x = "demo", int y = 5) { //do something... } public void Main() { //调用 Demo3(); // x,y不传入实参时,x,y使用默认值 "demo",5 Demo3("similar"); // y不传入实参时,y使用默认值5 Demo3("similar", 10); // x,y都传入实参 }
分先后。
b. 参数声明定义可以无顺序,但调用时必须与声明时的一致。
上面的调用只写的3种,其实还有一种,就是 x 使用默认值,y 传入实参,即 : Demo3(10);
但这样调用会报错,因为Demo3的第一个参数是 string 类型,错误消息如图:
但是现在我只想传入y, 不想传入 x ,该怎么办呢,那么就要用到 C#的 具名参数。
三. 具名参数
具名参数的使用主要是体现在函数调用的时候。
public void Main() { //调用 Demo3(); // x,y不传入实参时,x,y使用默认值 "demo",5 Demo3("similar"); // y不传入实参时,y使用默认值5 Demo3("similar", 10); // x,y都传入实参 // 具名参数的使用 Demo3(y:10); }
通过具名参数,我们可以指定特定参数的值,这里我们通过 Demo3(y:10)就可以解决我们上面遇到的问题(x使用默认值,y使用实参)。
注: 当使用 具名参数时,调用方法可以不用管参数的声明顺序,即如下调用方式也是可以的:
在调用含有可选参数的方法时,vs中会有智能提示,提示哪些是可以选参数及其默认值,中括号表示可选[]:
接口定义了一系列的行为规范,为类型定义一种Can-Do的功能。例如,实现IEnumerable接口定义了GetEnumerator方法,用于获取一个枚举数,该枚举数支持在集合上进行迭代,也就是我们常说的foreach。接口只是定义行为,具体的实现需要由具体类型负责,实现接口的方法又分为隐式实现与显示实现。
一、隐式/显示实现接口方法
简单的说,我们平时“默认”使用的都是隐式的实现方式。例如:
interface ILog { void Log(); } public class FileLogger : ILog { public void Log() { Console.WriteLine("记录到文件!"); } }
隐式实现很简单,通常我们约定接口命名以 I 开头,方便阅读。接口内的方法不需要用public,编译器会自动加上。类型中实现接口的方法只能是public,也可以定义成虚方法,由子类重写。现在看显示实现的方式:
public class EventLogger : ILog { void ILog.Log() { Console.WriteLine("记录到系统事件!"); } }
与上面不同的是,方法用了ILog指明,而且没有(也不能有)public或者private修饰符。
除了语法上的不同,调用方式也不同,显示实现只能用接口类型的变量来调用,如:
FileLogger fileLogger = new FileLogger(); fileLogger.Log(); //正确 EventLogger eventLogger = new EventLogger(); eventLogger.Log(); //报错 ILog log = new EventLogger(); log.Log(); //正确
二、何时使用
1. c#允许实现多个接口,如果多个接口定义了相同的方法,可以用显示实现的方式加以区分,例如:
public class EmailLogger : ILog, ISendable { void ILog.Log() { Console.WriteLine("ILog"); } void ISendable.Log() { Console.WriteLine("ISendable"); } }
2. 增强编译时的类型安全和避免值类型装箱
有了泛型,我们自然可以做到编译时的类型安全和避免值类型装箱的操作。但有时候可能没有对应的泛型版本。例如:IComparable(这里只是举例,实际有IComparable
interface IComparable { int CompareTo(object obj); } struct ValueType : IComparable { private int x; public ValueType(int x) { this.x = x; } public int CompareTo(object obj) { return this.x - ((ValueType)obj).x; } } //调用: ValueType vt1 = new ValueType(1); ValueType vt2 = new ValueType(2); Console.WriteLine(vt1.CompareTo(vt2));
由于形参是object,上面的CompareTo会发生装箱;而且无法获得编译时的类型安全,例如我们可以随便传一个string,编译不会报错,等到运行时才抛出InvalidCastException。使用显示实现接口的方式,如:
public int CompareTo(ValueType vt) { return this.x - vt.x; } int IComparable.CompareTo(object obj) { return CompareTo((ValueType)obj); }
再次执行上面的代码,就不会发生装箱操作,而且可以获得编译时的类型安全了。但是如果我们用接口变量调用,就会再次发生装箱并丧失编译时的类型安全检测能力
IComparable vt1 = new ValueType(1); //装箱 ValueType vt2 = new ValueType(2); Console.WriteLine(vt1.CompareTo(vt2)); //再次装箱
三、缺点
1. 显示实现只能用接口类型变量调用,会给人的感觉是某类型实现了该接口却无法调用接口中的方法。特别是写成类库给别人调用时,显示实现的接口方法在vs中按f12都不会显示出来。(这点有人在csdn提问过,为什么某个类型可以不用实现接口方法)
2. 对于值类型,要调用显示实现的方法,会发生装箱操作。
3. 无法被子类继承使用。
对异步CTP感兴趣有很多原因。异步CTP使异步编程比以前更加容易了。它虽然没有Rx强大,但是更容易学。异步CTP介绍了两个新的关键字,async和await。异步方法(或Lambda表达式)必须返回void,Task或Task
推断返回类型
当从异步方法返回一个值的时候,此方法体直接返回这个值,但该方法本身被声明为返回一个Task
// 实际语法 public async TaskGetValue() { await TaskEx.Delay(100); return 13; //返回类型是 "int", 而不是"Task " }
问题来了:为什么我不能这么写?
// 假想语法 public async int GetValue() { await TaskEx.Delay(100); return 13; // 返回类型是 "int" }
异步返回
// 假想语法 public async TaskGetValue() { await TaskEx.Delay(100); async return 13; // "async return" 意味着值被包装在Task中 }
async return关键字也被考虑到了,但并没有足够的说服力。当把一些同步代码转成异步代码时,这尤其正确。强制人们给每个return语句添加asynchronous就好像是“不必要的忙碌”。比较而言,习惯于“断连”更容易。
推断“async”
async关键字必须用在使用了await关键字的方法上。然而,如果把async用在了一个没有使用await的方法上,也会收到一个警告。
问题:为什么async不能根据await的存在推断出来?
//假想语法 public TaskGetValue() { // "await" 的存在暗示这是一个 "async" 方法. await TaskEx.Delay(100); return 13; }
思考:向后兼容性和代码可读性
单字的await关键字具有太大的打破变化。在异步方法上的多字await(如await for)或一个关键字之间的选择,只是在那个方法内部启用await关键字。很明显,使用async标记方法让人类和计算机分析起来更容易,因此设计团队决定使用async/await对。
推断“await”
问题:既然显示包括async有意义(看上面),为什么await不能根据async的存在推断出来呢?
// 假想语法 public async TaskGetValue() { // 暗示有"await",因为这是一个 "async" 方法. TaskEx.Delay(100); return 13; }
思考:异步操作的并行组合。
乍一看,推断await推断似乎简化了基本的异步操作。只要所有的等待可以按序列(如一个操作等待,然后另一个,再然后另一个)完成,这个就能很好的工作。然而,当有人考虑并行组合的时候,它崩溃了。
异步CTP中的并行组合使用TaskEx.WhenAny 和TaskEx.WhenAll方法。这有一个简单的例子,这个方法立即开始了两个操作,并且等待它们完成。
// 实际语法 public async TaskGetValue() { // 异步检索两个部分的值 // 注意此时它们是没有等待的“not await” Task part1 = GetValuePart1(); Task part2 = GetValuePart2(); // 等待它们的值到达。 await TaskEx.WhenAll(part1, part2); // 计算我们的结果 int value1 = await part1; // 实际上没有等待 int value2 = await part2; //实际上没有等待 return value1 + value2; }
为了处理并行组合,我们必须有能力说我们将不会await一个表达式。
一、创建线程
在整个系列文章中,我们主要使用Visual Studio 2015作为线程编程的主要工具。在C#语言中创建、使用线程只需要按以下步骤编写即可:
1、启动Visual Studio 2016,新建一个控制台应用程序。
2、确保该控制台程序使用.NET Framework 4.6或以上版本。然而在该篇中的所有示例使用较低版本可以正常工作。
3、双击打开该控制台应用程序中的“Program.cs”文件,在其中编写如下代码:
using System; using System.Threading; using static System.Console; namespace Recipe01 { class Program { static void PrintNumbers() { WriteLine("Starting..."); for (int i = 1; i < 10; i++) { WriteLine(i); } } static void Main(string[] args) { Thread t = new Thread(PrintNumbers); t.Start(); PrintNumbers(); } } }
在第2行代码处,我们导入了System.Threading命名空间,该命名空间包含了我们编写多线程程序所需要的所有类型。
在第3行代码处,我们使用了C# 6.0的using static特性,使用了该特性之后,在代码中允许我们在使用System.Console类型的静态方法的时候不需要指定其类型名。
在第9~16行代码处,我们定义了一个名为“PrintNumbers”的方法,该方法将在“Main”方法和线程中进行调用。
在第20行代码处,我们创建了一个线程来运行“PrintNumbers”方法,当我们初始化一个线程时,一个“ThreadStart”或“ParameterizedThreadStart”委托的实例被传递给线程的构造方法。
在第21行代码处,我们启动线程。
在第22行代码处,我们在“Main”方法中调用“PrintNumbers”方法。
4、运行该控制台应用程序,运行效果(每次运行效果可能不同)如下图所示:
二、中止线程
在这一节,我们将让线程等待一些时间,在等待的这段时间内,该线程不会消耗操作系统的资源。编写步骤如下:
1、使用Visual Studio 2015创建一个新的控制台应用程序。
2、双击打开“Program.cs”文件,编写代码如下所示:
using System; using System.Threading; using static System.Console; namespace Recipe01 { class Program { static void PrintNumbers() { WriteLine("Starting..."); for (int i = 1; i < 10; i++) { WriteLine(i); } } static void Main(string[] args) { Thread t = new Thread(PrintNumbers); t.Start(); PrintNumbers(); } } }
3、运行该控制台应用程序,运行效果(每次运行效果可能不同)如下图所示:
三、线程等待
在这一节中,我们将讲述如何在一个线程执行完毕后,再执行剩余的代码,要完成这个工作,我们不能使用Thread.Sleep方法,因为我们不知道另一个线程精确的执行时间。要使一个线程等待另一个线程执行完毕后再进行其他工作,只需要按下列步骤编写代码即可:
1、使用Visual Studio 2015创建一个新的控制台应用程序。
2、双击打开“Program.cs”文件,编写如下代码:
using System; using System.Threading; using static System.Console; using static System.Threading.Thread; namespace Recipe03 { class Program { static void PrintNumbersWithDelay() { WriteLine("Starting..."); for(int i = 1; i < 10; i++) { Sleep(TimeSpan.FromSeconds(2)); WriteLine(i); } } static void Main(string[] args) { WriteLine("Starting..."); Thread t = new Thread(PrintNumbersWithDelay); t.Start(); t.Join(); WriteLine("Thread completed"); } } }
3、运行该控制台应用程序,运行效果如下图所示:
在第26行代码处,我们在“Main”方法中调用调用“t.Join”方法,该方法允许我们等待线程t执行完毕后,再执行“Main”方法中剩余的代码。有了该技术,我们可以同步两个线程的执行步骤。第一个线程等待第二个线程执行完毕后,再进行其他的工作,在第一个线程等待期间,第一个线程的状态为“bolcked”状态,和我们调用Thread.Sleep的状态一样。
四、终止线程
在这一节中,我们将讲述如何终止另一个线程的执行。步骤如下:
1、使用Visual Studio 2015创建一个新的控制台应用程序。
2、双击打开“Program.cs”文件,编写如下代码:
using System; using System.Threading; using static System.Console; using static System.Threading.Thread; namespace Recipe04 { class Program { static void PrintNumbers() { WriteLine("Starting..."); for (int i = 1; i < 10; i++) { WriteLine(i); } } static void PrintNumbersWithDelay() { WriteLine("Starting..."); for (int i = 1; i < 10; i++) { Sleep(TimeSpan.FromSeconds(2)); WriteLine(i); } } static void Main(string[] args) { WriteLine("Starting program..."); Thread t = new Thread(PrintNumbersWithDelay); t.Start(); Thread.Sleep(TimeSpan.FromSeconds(6)); t.Abort(); WriteLine("A thread has been aborted"); t = new Thread(PrintNumbers); t.Start(); PrintNumbers(); } } }
3、运行该控制台应用程序,运行效果(每次运行效果可能不同)如下图所示:
在对Bitmap图片操作的时候,有时需要用到获取或设置像素颜色方法:GetPixel 和 SetPixel,
如果直接对这两个方法进行操作的话速度很慢,这里我们可以通过把数据提取出来操作,然后操作完在复制回去可以加快访问速度
其实对Bitmap的访问还有两种方式,一种是内存法,一种是指针法
1、内存法
这里定义一个类LockBitmap,通过把Bitmap数据拷贝出来,在内存上直接操作,操作完成后在拷贝到Bitmap中
public class LockBitmap { Bitmap source = null; IntPtr Iptr = IntPtr.Zero; BitmapData bitmapData = null; public byte[] Pixels { get; set; } public int Depth { get; private set; } public int Width { get; private set; } public int Height { get; private set; } public LockBitmap(Bitmap source) { this.source = source; } ////// Lock bitmap data /// public void LockBits() { try { // Get width and height of bitmap Width = source.Width; Height = source.Height; // get total locked pixels count int PixelCount = Width * Height; // Create rectangle to lock Rectangle rect = new Rectangle(0, 0, Width, Height); // get source bitmap pixel format size Depth = System.Drawing.Bitmap.GetPixelFormatSize(source.PixelFormat); // Check if bpp (Bits Per Pixel) is 8, 24, or 32 if (Depth != 8 && Depth != 24 && Depth != 32) { throw new ArgumentException("Only 8, 24 and 32 bpp images are supported."); } // Lock bitmap and return bitmap data bitmapData = source.LockBits(rect, ImageLockMode.ReadWrite, source.PixelFormat); // create byte array to copy pixel values int step = Depth / 8; Pixels = new byte[PixelCount * step]; Iptr = bitmapData.Scan0; // Copy data from pointer to array Marshal.Copy(Iptr, Pixels, 0, Pixels.Length); } catch (Exception ex) { throw ex; } } ////// Unlock bitmap data /// public void UnlockBits() { try { // Copy data from byte array to pointer Marshal.Copy(Pixels, 0, Iptr, Pixels.Length); // Unlock bitmap data source.UnlockBits(bitmapData); } catch (Exception ex) { throw ex; } } ////// Get the color of the specified pixel /// /// /// ///public Color GetPixel(int x, int y) { Color clr = Color.Empty; // Get color components count int cCount = Depth / 8; // Get start index of the specified pixel int i = ((y * Width) + x) * cCount; if (i > Pixels.Length - cCount) throw new IndexOutOfRangeException(); if (Depth == 32) // For 32 bpp get Red, Green, Blue and Alpha { byte b = Pixels[i]; byte g = Pixels[i + 1]; byte r = Pixels[i + 2]; byte a = Pixels[i + 3]; // a clr = Color.FromArgb(a, r, g, b); } if (Depth == 24) // For 24 bpp get Red, Green and Blue { byte b = Pixels[i]; byte g = Pixels[i + 1]; byte r = Pixels[i + 2]; clr = Color.FromArgb(r, g, b); } if (Depth == 8) // For 8 bpp get color value (Red, Green and Blue values are the same) { byte c = Pixels[i]; clr = Color.FromArgb(c, c, c); } return clr; } /// /// Set the color of the specified pixel /// /// /// /// public void SetPixel(int x, int y, Color color) { // Get color components count int cCount = Depth / 8; // Get start index of the specified pixel int i = ((y * Width) + x) * cCount; if (Depth == 32) // For 32 bpp set Red, Green, Blue and Alpha { Pixels[i] = color.B; Pixels[i + 1] = color.G; Pixels[i + 2] = color.R; Pixels[i + 3] = color.A; } if (Depth == 24) // For 24 bpp set Red, Green and Blue { Pixels[i] = color.B; Pixels[i + 1] = color.G; Pixels[i + 2] = color.R; } if (Depth == 8) // For 8 bpp set color value (Red, Green and Blue values are the same) { Pixels[i] = color.B; } } }
使用:先锁定Bitmap,然后通过Pixels操作颜色对象,最后释放锁,把数据更新到Bitmap中
string file = @"C:\test.jpg"; Bitmap bmp = new Bitmap(Image.FromFile(file)); LockBitmap lockbmp = new LockBitmap(bmp); //锁定Bitmap,通过Pixel访问颜色 lockbmp.LockBits(); //获取颜色 Color color = lockbmp.GetPixel(10, 10); //从内存解锁Bitmap lockbmp.UnlockBits();
2、指针法
这种方法访问速度比内存法更快,直接通过指针对内存进行操作,不需要进行拷贝,但是在C#中直接通过指针操作内存是不安全的,所以需要在代码中加入unsafe关键字,在生成选项中把允许不安全代码勾上,才能编译通过
这里定义成PointerBitmap类
public class PointBitmap { Bitmap source = null; IntPtr Iptr = IntPtr.Zero; BitmapData bitmapData = null; public int Depth { get; private set; } public int Width { get; private set; } public int Height { get; private set; } public PointBitmap(Bitmap source) { this.source = source; } public void LockBits() { try { // Get width and height of bitmap Width = source.Width; Height = source.Height; // get total locked pixels count int PixelCount = Width * Height; // Create rectangle to lock Rectangle rect = new Rectangle(0, 0, Width, Height); // get source bitmap pixel format size Depth = System.Drawing.Bitmap.GetPixelFormatSize(source.PixelFormat); // Check if bpp (Bits Per Pixel) is 8, 24, or 32 if (Depth != 8 && Depth != 24 && Depth != 32) { throw new ArgumentException("Only 8, 24 and 32 bpp images are supported."); } // Lock bitmap and return bitmap data bitmapData = source.LockBits(rect, ImageLockMode.ReadWrite, source.PixelFormat); //得到首地址 unsafe { Iptr = bitmapData.Scan0; //二维图像循环 } } catch (Exception ex) { throw ex; } } public void UnlockBits() { try { source.UnlockBits(bitmapData); } catch (Exception ex) { throw ex; } } public Color GetPixel(int x, int y) { unsafe { byte* ptr = (byte*)Iptr; ptr = ptr + bitmapData.Stride * y; ptr += Depth * x / 8; Color c = Color.Empty; if (Depth == 32) { int a = ptr[3]; int r = ptr[2]; int g = ptr[1]; int b = ptr[0]; c = Color.FromArgb(a, r, g, b); } else if (Depth == 24) { int r = ptr[2]; int g = ptr[1]; int b = ptr[0]; c = Color.FromArgb(r, g, b); } else if (Depth == 8) { int r = ptr[0]; c = Color.FromArgb(r, r, r); } return c; } } public void SetPixel(int x, int y, Color c) { unsafe { byte* ptr = (byte*)Iptr; ptr = ptr + bitmapData.Stride * y; ptr += Depth * x / 8; if (Depth == 32) { ptr[3] = c.A; ptr[2] = c.R; ptr[1] = c.G; ptr[0] = c.B; } else if (Depth == 24) { ptr[2] = c.R; ptr[1] = c.G; ptr[0] = c.B; } else if (Depth == 8) { ptr[2] = c.R; ptr[1] = c.G; ptr[0] = c.B; } } } }
使用方法这里就不列出来了,跟上面的LockBitmap类似
本文实例讲述了C#对图片文件的压缩、裁剪操作方法,在C#项目开发中非常有实用价值。分享给大家供大家参考。具体如下:
一般在做项目时,对图片的处理,以前都采用在上传时,限制其大小的方式,这样带来诸多不便。毕竟网站运维人员不一定会对图片做处理,经常超出大小限制,即使会使用图片处理软件的,也由于个人水平方面原因,处理效果差强人意。
于是采用C#为我们提供的图像编辑功能,实现一站式上传,通过程序生成所需大小、尺寸的目标图片。
具体步骤如下:
先说图片压缩:
第一步:需要读取一个图片文件,读取方法:
// 图片文件的全路径名称 public Image ResourceImage =Image.FromFile(ImageFilePathAndName);
说明:
Image类:引用自System.Drawing,为源自 Bitmap 和 Metafile 的类提供功能的抽象基类。
主要属性:Size->获取此图像的以像素为单位的宽度和高度。
PhysicalDimension->获取此图像的宽度和高度(如果该图像是位图,以像素为单位返回宽度和高度。如果该图像是图元文件,则以0.01 毫米为单位返回宽度和高度。)。
PixelFormat->获取此 Image 的像素格式。
Height、Width->获取此 Image 的高度、宽度(以像素为单位)。
主要方法:FromFile(String)->从指定的文件创建 Image。
FromStream(Stream)->从指定的数据流创建 Image。
Save(String fileName)->将该 Image 保存到指定的文件或流。
Save(Stream, ImageFormat)->将此图像以指定的格式保存到指定的流中。
Save(String, ImageFormat)->将此 Image 以指定格式保存到指定文件。
更多属性和方法说明请点击。
第二步,生成缩略图,并且将原图内容按指定大小绘制到目标图片。
////// 生成缩略图重载方法1,返回缩略图的Image对象 /// /// 缩略图的宽度 /// 缩略图的高度 ///缩略图的Image对象 public Image GetReducedImage(int Width, int Height) { try { //用指定的大小和格式初始化Bitmap类的新实例 Bitmap bitmap = new Bitmap(Width, Height, PixelFormat.Format32bppArgb); //从指定的Image对象创建新Graphics对象 Graphics graphics = Graphics.FromImage(bitmap); //清除整个绘图面并以透明背景色填充 graphics.Clear(Color.Transparent); //在指定位置并且按指定大小绘制原图片对象 graphics.DrawImage(ResourceImage, new Rectangle(0, 0, Width, Height)); return bitmap; } catch (Exception e) { ErrMessage = e.Message; return null; } }
说明:
1、Bitmap类
引用自System.Drawing,封装 GDI+ 位图,此位图由图形图像及其特性的像素数据组成。Bitmap 是用于处理由像素数据定义的图像的对象。
关于封装图像的对象,详细介绍可参看官方文档:http://msdn.microsoft.com/zh-cn/library/system.drawing.bitmap.aspx。
2、Graphics类
引用自System.Drawing,(处理图像的对象),封装一个 GDI+ 绘图图面。
关于Graphics类可点此查看官方教程:http://msdn.microsoft.com/zh-cn/library/system.drawing.graphics.aspx。
第三步,保存
第二步操作中返回的Image对象,暂时命名为:iImage:
iImage.Save(pathAndName, System.Drawing.Imaging.ImageFormat.Jpeg);
以上是压缩操作,做了下试验,101k的图片,经过压缩后是57k。这个应该和尺寸有关系。
以下是图片裁剪,其实原理和上面相似,无非也就是对图片进行重画操作。
////// 截取图片方法 /// /// 图片地址 /// 开始位置-X /// 开始位置-Y /// 截取宽度 /// 截取长度 /// 文件名称 /// 保存路径 /// 后缀名 public static string CutImage(string url, int beginX, int beginY, int getX, int getY, string fileName, string savePath, string fileExt) { if ((beginX < getX) && (beginY < getY)) { Bitmap bitmap = new Bitmap(url);//原图 if (((beginX + getX) <= bitmap.Width) && ((beginY + getY) <= bitmap.Height)) { Bitmap destBitmap = new Bitmap(getX, getY);//目标图 Rectangle destRect = new Rectangle(0, 0, getX, getY);//矩形容器 Rectangle srcRect = new Rectangle(beginX, beginY, getX, getY); Graphics.FromImage(destBitmap); Graphics.DrawImage(bitmap, destRect, srcRect, GraphicsUnit.Pixel); ImageFormat format = ImageFormat.Png; switch (fileExt.ToLower()) { case "png": format = ImageFormat.Png; break; case "bmp": format = ImageFormat.Bmp; break; case "gif": format = ImageFormat.Gif; break; } destBitmap.Save(savePath + "//" + fileName , format); return savePath + "\\" + "*" + fileName.Split('.')[0] + "." + fileExt; } else { return "截取范围超出图片范围"; } } else { return "请确认(beginX < getX)&&(beginY < getY)"; } }
说明:
Rectangle类:矩形,详情可参考官方文档:http://msdn.microsoft.com/zh-cn/library/system.windows.shapes.rectangle(v=vs.85).aspx