如下爲本次實踐代碼:程序員
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(); } } /// <summary> /// 我的信息 /// </summary> 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的話,也能夠以下:web
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); } } /// <summary> /// 我的信息 /// </summary> 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#中定義函數express
//函數定義,參數爲string,返回爲string Func<string, string> myFunc = delegate(string msg) { return "Msg:" + msg; };
利用Lambda表達式也能夠簡化上述的代碼,可是效果同樣:編程
//Lambda Func<string, string> myFuncSame = msg => "Msg:" + msg;
定義好函數後,能夠進行調用:c#
//函數調用 string message= myFuncSame("Hello world");
定義一個帶外部變量(相對於內嵌函數而言)的嵌套函數,外部函數將內部嵌套的函數進行返回:windows
public static Func<int, int> Func() { var myVar = 1; Func<int, int> 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加密,算法代碼實現以下:
/// <summary> /// MD5加密 /// </summary> /// <param name="str">加密字元</param> /// <param name="code">加密位數16/32</param> /// <returns></returns> 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 <expression>; yield break;
使用一個yield return語句返回集合的一個元素
包含yield語句的方法或屬性是迭代器。迭代器必須知足如下要求
a. 返回類型必須是IEnumerable、IEnumerable<T>、IEnumerator或 IEnumerator<T>。
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 IEnumerator<String> GetEnumerator() { // 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 IEnumerator<string> GetEnumerator() { for (int i = 0; i < 4; i++) { yield return names[i]; } } public IEnumerable<string> Reverse() { for (int i = 3; i >= 0; i--) { yield return names[i]; } } public IEnumerable<string> 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<T>泛型接口的,Array類並無實現這些泛型接口,爲何可使用這些方法呢?
linq to objects的本質是經過擴展方法來實現集合的查詢,這些擴展方法定義在一個Enumerable的靜態類中。Enumerable類下的全部擴展方法的第一個參數都是IEnumerable<T> 類型,表示它能夠經過IEnumerable<T>類型進行調用。
淺析數組類型
1. 全部數組類型都隱式派生自Array
當咱們定義一個FileStream[] 數組時,CLR會爲當前的AppDomain建立一個FileStream[] 類型,該類型派生自 Array。因此數組是引用類型,在堆中分配內存空間。Array類是一個抽象類,定義了許多關於經常使用的實例方法和靜態方法,供全部的數組類型使用。例如常見的:Length屬性,CopyTo方法等等。
2. 全部的數組類型都隱式實現了IEnumerable<T>接口
就如上面所所的,這是一個理所固然的問題,爲了提升開發效率,數組類型理應可使用linq進行查詢。但因爲數組能夠是多維數組或者非0基數組,因此Array類並無實現IEnumerable<T>、ICollection<T>、IList<T> 這幾個泛型接口,而只是實現了非泛型版本的。實際上,CLR會自動爲一維的數組類型實現這些泛型接口(指定T類型參數的具體類型),而且還會爲它們的父類實現。例如咱們定義一個FileStream[] 數組類型,那麼CLR會爲咱們建立以下的層次類型結構:
因爲CLR的隱式實現,才使咱們能夠將一維數組類型應用在須要IEnumerable<T>泛型接口的地方。
按照上面的說法,咱們能夠將FileStream[] 類型的對象傳遞給以下的方法:
void F1(IEnumerable<object> oEnumerable);
void F2(ICollection<Stream> sCollection);
void F3(IList<FileStream> fList);
這是對於引用類型而言的,若是是值類型,則不爲會它的基類實現這些接口。例如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。接口只是定義行爲,具體的實現須要由具體類型負責,實現接口的方法又分爲隱式實現與顯示實現。
1、隱式/顯示實現接口方法
簡單的說,咱們平時「默認」使用的都是隱式的實現方式。例如:
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(); //正確
2、什麼時候使用
1. c#容許實現多個接口,若是多個接口定義了相同的方法,能夠用顯示實現的方式加以區分,例如:
public class EmailLogger : ILog, ISendable { void ILog.Log() { Console.WriteLine("ILog"); } void ISendable.Log() { Console.WriteLine("ISendable"); } }
2. 加強編譯時的類型安全和避免值類型裝箱
有了泛型,咱們天然能夠作到編譯時的類型安全和避免值類型裝箱的操做。但有時候可能沒有對應的泛型版本。例如:IComparable(這裏只是舉例,實際有IComparable<T>)。如:
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)); //再次裝箱
3、缺點
1. 顯示實現只能用接口類型變量調用,會給人的感受是某類型實現了該接口卻沒法調用接口中的方法。特別是寫成類庫給別人調用時,顯示實現的接口方法在vs中按f12都不會顯示出來。(這點有人在csdn提問過,爲何某個類型能夠不用實現接口方法)
2. 對於值類型,要調用顯示實現的方法,會發生裝箱操做。
3. 沒法被子類繼承使用。
對異步CTP感興趣有不少緣由。異步CTP使異步編程比之前更加容易了。它雖然沒有Rx強大,可是更容易學。異步CTP介紹了兩個新的關鍵字,async和await。異步方法(或Lambda表達式)必須返回void,Task或Task<TResult>。這篇文章不是介紹異步CTP的,由於網上有不少這樣的文章。這篇文章的目的是把程序員開始使用Async CTP遇到的一些常見問題集中起來。
當從異步方法返回一個值的時候,此方法體直接返回這個值,但該方法自己被聲明爲返回一個Task<TResult>。當聲明一個返回甲類型的方法卻必須返回一個乙類型時,就有點「斷連」了。
// 實際語法 public async Task<int> GetValue() { await TaskEx.Delay(100); return 13; //返回類型是 "int", 而不是"Task<int>" }
問題來了:爲何我不能這麼寫?
// 假想語法 public async int GetValue() { await TaskEx.Delay(100); return 13; // 返回類型是 "int" }
// 假想語法 public async Task<int> GetValue() { await TaskEx.Delay(100); async return 13; // "async return" 意味着值被包裝在Task中 }
async return關鍵字也被考慮到了,但並無足夠的說服力。當把一些同步代碼轉成異步代碼時,這尤爲正確。強制人們給每一個return語句添加asynchronous就好像是「沒必要要的忙碌」。比較而言,習慣於「斷連」更容易。
async關鍵字必須用在使用了await關鍵字的方法上。然而,若是把async用在了一個沒有使用await的方法上,也會收到一個警告。
問題:爲何async不能根據await的存在推斷出來?
//假想語法 public Task<int> GetValue() { // "await" 的存在暗示這是一個 "async" 方法. await TaskEx.Delay(100); return 13; }
思考:向後兼容性和代碼可讀性
單字的await關鍵字具備太大的打破變化。在異步方法上的多字await(如await for)或一個關鍵字之間的選擇,只是在那個方法內部啓用await關鍵字。很明顯,使用async標記方法讓人類和計算機分析起來更容易,所以設計團隊決定使用async/await對。
問題:既然顯示包括async有意義(看上面),爲何await不能根據async的存在推斷出來呢?
// 假想語法 public async Task<int> GetValue() { // 暗示有"await",由於這是一個 "async" 方法. TaskEx.Delay(100); return 13; }
思考:異步操做的並行組合。
乍一看,推斷await推斷彷佛簡化了基本的異步操做。只要全部的等待能夠按序列(如一個操做等待,而後另外一個,再而後另外一個)完成,這個就能很好的工做。然而,當有人考慮並行組合的時候,它崩潰了。
異步CTP中的並行組合使用TaskEx.WhenAny 和TaskEx.WhenAll方法。這有一個簡單的例子,這個方法當即開始了兩個操做,而且等待它們完成。
// 實際語法 public async Task<int> GetValue() { // 異步檢索兩個部分的值 // 注意此時它們是沒有等待的「not await」 Task<int> part1 = GetValuePart1(); Task<int> part2 = GetValuePart2(); // 等待它們的值到達。 await TaskEx.WhenAll(part1, part2); // 計算咱們的結果 int value1 = await part1; // 實際上沒有等待 int value2 = await part2; //實際上沒有等待 return value1 + value2; }
爲了處理並行組合,咱們必須有能力說咱們將不會await一個表達式。
1、建立線程
在整個系列文章中,咱們主要使用Visual Studio 2015做爲線程編程的主要工具。在C#語言中建立、使用線程只須要按如下步驟編寫便可:
一、啓動Visual Studio 2016,新建一個控制檯應用程序。
二、確保該控制檯程序使用.NET Framework 4.6或以上版本。然而在該篇中的全部示例使用較低版本能夠正常工做。
三、雙擊打開該控制檯應用程序中的「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」方法。
四、運行該控制檯應用程序,運行效果(每次運行效果可能不一樣)以下圖所示:
2、停止線程
在這一節,咱們將讓線程等待一些時間,在等待的這段時間內,該線程不會消耗操做系統的資源。編寫步驟以下:
一、使用Visual Studio 2015建立一個新的控制檯應用程序。
二、雙擊打開「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方法,由於咱們不知道另外一個線程精確的執行時間。要使一個線程等待另外一個線程執行完畢後再進行其餘工做,只須要按下列步驟編寫代碼便可:
一、使用Visual Studio 2015建立一個新的控制檯應用程序。
二、雙擊打開「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"); } } }
三、運行該控制檯應用程序,運行效果以下圖所示:
在第26行代碼處,咱們在「Main」方法中調用調用「t.Join」方法,該方法容許咱們等待線程t執行完畢後,再執行「Main」方法中剩餘的代碼。有了該技術,咱們能夠同步兩個線程的執行步驟。第一個線程等待第二個線程執行完畢後,再進行其餘的工做,在第一個線程等待期間,第一個線程的狀態爲「bolcked」狀態,和咱們調用Thread.Sleep的狀態同樣。
4、終止線程
在這一節中,咱們將講述如何終止另外一個線程的執行。步驟以下:
一、使用Visual Studio 2015建立一個新的控制檯應用程序。
二、雙擊打開「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(); } } }
三、運行該控制檯應用程序,運行效果(每次運行效果可能不一樣)以下圖所示:
在對Bitmap圖片操做的時候,有時須要用到獲取或設置像素顏色方法:GetPixel 和 SetPixel,
若是直接對這兩個方法進行操做的話速度很慢,這裏咱們能夠經過把數據提取出來操做,而後操做完在複製回去能夠加快訪問速度
其實對Bitmap的訪問還有兩種方式,一種是內存法,一種是指針法
一、內存法
這裏定義一個類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; } /// <summary> /// Lock bitmap data /// </summary> 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; } } /// <summary> /// Unlock bitmap data /// </summary> 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; } } /// <summary> /// Get the color of the specified pixel /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <returns></returns> 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; } /// <summary> /// Set the color of the specified pixel /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <param name="color"></param> 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();
二、指針法
這種方法訪問速度比內存法更快,直接經過指針對內存進行操做,不須要進行拷貝,可是在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#爲咱們提供的圖像編輯功能,實現一站式上傳,經過程序生成所需大小、尺寸的目標圖片。
具體步驟以下:
先說圖片壓縮:
第一步:須要讀取一個圖片文件,讀取方法:
// <param name="ImageFilePathAndName">圖片文件的全路徑名稱</param> 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 以指定格式保存到指定文件。
更多屬性和方法說明請點擊。
第二步,生成縮略圖,而且將原圖內容按指定大小繪製到目標圖片。
/// <summary> /// 生成縮略圖重載方法1,返回縮略圖的Image對象 /// </summary> /// <param name="Width">縮略圖的寬度</param> /// <param name="Height">縮略圖的高度</param> /// <returns>縮略圖的Image對象</returns> 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; } }
說明:
一、Bitmap類
引用自System.Drawing,封裝 GDI+ 位圖,此位圖由圖形圖像及其特性的像素數據組成。Bitmap 是用於處理由像素數據定義的圖像的對象。
關於封裝圖像的對象,詳細介紹可參看官方文檔:http://msdn.microsoft.com/zh-cn/library/system.drawing.bitmap.aspx。
二、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。這個應該和尺寸有關係。
如下是圖片裁剪,其實原理和上面類似,無非也就是對圖片進行重畫操做。
/// <summary> /// 截取圖片方法 /// </summary> /// <param name="url">圖片地址</param> /// <param name="beginX">開始位置-X</param> /// <param name="beginY">開始位置-Y</param> /// <param name="getX">截取寬度</param> /// <param name="getY">截取長度</param> /// <param name="fileName">文件名稱</param> /// <param name="savePath">保存路徑</param> /// <param name="fileExt">後綴名</param> 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