前言
記錄一下C#的一些東西,基礎好多還是不會,還是推薦微軟的官網文檔,網上的博客寫的都太水了,還是官網文檔好一點
異步任務
同步方法的缺點
其實我最想講的就是這個,我舉個例子,有兩個方法,方法1和方法2,我現在想先執行方法1再執行方法2,如果我順序執行的話,那么必須等待方法1執行完成之后才執行方法2 代碼如下
static void Main(string[] args)
{
method1();
method2();
}
public static void method1()
{
for (int i = 0; i < 80; i++)
{
System.Console.WriteLine("method1: "+i);
}
}
public static void method2() {
for (int i = 0; i < 20; i++)
{
System.Console.WriteLine("method2: "+i);
}
}
執行一下就知道了,必須等待方法1執行完才會執行方法2,就比如我想燒水做飯,必須先等水燒開了我才能洗菜切菜......這明明是可以同時做的事情,我們可以使用異步方法解決
異步方法
這個分為兩種情況,I/O和CPU運算,我這里暫時沒用到I/O所以不寫了,講講CPU運算的
返回Task
static void Main(string[] args)
{
method1();
method2();
System.Console.ReadKey();
}
public static async Task method1()
{
await Task.Run(() =>
{
for (int i = 0; i < 80; i++)
{
System.Console.WriteLine("method1: " + i);
}
});
}
public static void method2() {
for (int i = 0; i < 20; i++)
{
System.Console.WriteLine("method2: "+i);
}
}
特點就是async,Task或者Task<T>
,await,Task.Run這幾個
返回Task<T>
static void Main(string[] args)
{
callMethod();
System.Console.ReadKey();
}
public static async void callMethod()
{
Task<int> task = method1();
int count = await task;
method3(count);
}
public static async Task<int> method1()
{
int count=0;
await Task.Run(() =>
{
for (int i = 0; i < 80; i++)
{
System.Console.WriteLine("method1: " + i);
count++;
}
});
return count;
}
public static void method2()
{
for (int i = 0; i < 20; i++)
{
System.Console.WriteLine("method2: " + i);
}
}
public static void method3(int count)
{
System.Console.WriteLine("Count is "+count);
}
C#讀取CSV,存入數據庫
C#讀取CSV的內容,以DataTable的格式返回
string path = @"D:\360MoveData\Users\Justin\Desktop\dgkdata\Audio Products~Accessories.csv";
public static DataTable ReadData(string filePath)
{
//Encoding encoding = Common.GetType(filePath); //Encoding.ASCII;//
Encoding encoding = Encoding.ASCII; //Encoding.ASCII;//
DataTable dt = new DataTable();
FileStream fs = new FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read);
//StreamReader sr = new StreamReader(fs, Encoding.UTF8);
StreamReader sr = new StreamReader(fs, encoding);
//string fileContent = sr.ReadToEnd();
//encoding = sr.CurrentEncoding;
//記錄每次讀取的一行記錄
string strLine = "";
//記錄每行記錄中的各字段內容
string[] aryLine = null;
string[] tableHead = null;
//標示列數
int columnCount = 0;
//標示是否是讀取的第一行
bool IsFirst = true;
//逐行讀取CSV中的數據
while ((strLine = sr.ReadLine()) != null)
{
if (IsFirst == true)
{
tableHead = strLine.Split(',');
IsFirst = false;
columnCount = tableHead.Length;
//創建列
for (int i = 0; i < columnCount; i++)
{
DataColumn dc = new DataColumn(tableHead[i]);
dt.Columns.Add(dc);
}
}
else
{
//MySplit這個方法看下面的介紹
List<string> dataList = MySplit(strLine);
aryLine = dataList.ToArray();
DataRow dr = dt.NewRow();
for (int j = 0; j < columnCount; j++)
{
dr[j] = aryLine[j];
}
dt.Rows.Add(dr);
}
}
if (aryLine != null && aryLine.Length > 0)
{
dt.DefaultView.Sort = tableHead[0] + " " + "asc";
}
sr.Close();
fs.Close();
return dt;
}
然后接受這個DataTable
//先獲取所有的列名
DataTable dt = Read.ReadData(path);
string[] strColumns = null;
if (dt.Columns.Count > 0)
{
int columnNum = 0;
columnNum = dt.Columns.Count;
strColumns = new string[columnNum];
for (int i = 0; i < dt.Columns.Count; i++)
{
strColumns[i] = dt.Columns[i].ColumnName;
}
}
//在遍歷開始處理數據
foreach (DataRow dataRow in dt.Rows)
{
foreach (var columsName in strColumns)
{
switch (columsName)
{
case "Datasheets":
break;
case "Image":
break;
處理邏輯......
}
string aaa = dataRow[columsName].ToString();
處理邏輯......
Console.WriteLine(aaa);
}
}
Split(',')過濾掉雙引號內的逗號
這個也可以叫做,C#讀取CSV文件逗號問題
我讀取的一串字符串是這樣的
"許嵩","蜀,雲泉",1,22,"音樂"
我使用Split(',')之后蜀雲泉就分開了,這顯然不是我要的結果
解決方法可以使用正則,但是我不會寫,所以寫一個最基礎的substring
private static List<string> MySplit(string str)
{
const char mark = '"';
const char comma = ',';
bool startMark = false;
int startIndex = -1;
int endIndex = -1;
List<string> myList = new List<string>();
for (int i = 0; i < str.Length; i++)
{
if (str[0] == comma)
{
myList.Add("");
}
if (startMark && str[i] == comma)
{
continue;
}
if (str[i] == comma && i > 0)
{
endIndex = i;
}
if (str[i] == mark && !startMark)
{
startMark = true;
}
else if (str[i] == mark && startMark)
{
startMark = false;
}
if (startIndex == -1)
{ startIndex = i; }
if ((startIndex >= 0 && endIndex > 0) || (endIndex == -1 && i == str.Length - 1))
{
if (endIndex == -1)
{
endIndex = i + 1;
}
myList.Add(str.Substring(startIndex, endIndex - startIndex));
startIndex = -1;
endIndex = -1;
}
}
return myList;
}
這個strLine就是C#讀取CSV的一行內容
用好代碼代替注釋
如開發人員發現需要寫注釋才能說清楚代碼塊的功用,應考慮重構,而不是洋洋灑灑寫一堆注釋。寫注釋來重復代碼本來就講得清的事情,只會變得臃腫,降低可讀性,還容易過時,因為將來可能更改代碼但沒有來得及更新注釋。
設計規范
-
不要使用注釋,除非代碼本身“一言難盡”。
-
要盡量寫清楚的代碼而不是通過注釋澄清復雜的算法。
C#的13種基元類型
所謂的基元類型,就是C#中的所有類型的基礎,分別有8種整數類型,2種小數類型,1種金融類型,1種布爾類型,1種字符類型:
金融類型是Decimal,布爾Bool,字符類型char
避免使用隱式類型
所謂的隱式類型就是var
var name = "許嵩";
string name = "許嵩";
我使用var或者string都是一樣的,在最終的CIL代碼里面也沒區別,但是,如果確定類型,還是直接指定類型好,一目了然
引用參數ref和輸出參數out
先說結論
-
ref參數:將變量帶入一個方法中改變之后在帶出方法,ref參數使用前必須賦值
-
out參數: 在返回多個值的時候使用out參數,使用前不需要賦值
舉個例子,代碼如下
static void Main(string[] args)
{
int salary = 5000;
jiangJin(salary);
Console.WriteLine(salary);
Console.Read();
}
static void jiangJin(int salary)
{
salary += 500;
}
像這個例子,輸出的salary還是5000,雖然我經過了jiangJin方法的計算,但是我沒有return計算后的結果,所以不管方法內怎么計算了,只要不return,salary值沒變
現在我加一個ref就不同了
static void Main(string[] args)
{
int salary = 5000;
jiangJin(ref salary);
Console.WriteLine(salary);
Console.Read();
}
static void jiangJin(ref int salary)
{
salary += 500;
}
我就加了一個ref,然后salary的輸出結果就是5500了,不需要return了
所以ref參數的作用是:將變量帶入一個方法中改變之后在帶出方法,以傳引用的方式來傳變量,而不是值拷貝的方式
out輸出參數其實和ref功能一模一樣,但是out輸出參數更注重檢查方法內是否對out參數進行賦值,代碼如下
static void Main(string[] args)
{
int a = 1;
int b;
int asd = Calcu(a,out b);
Console.WriteLine(asd + " : " + b);
Console.Read();
}
static int Calcu(int a, out int b)
{
b = a;
return a + b;
}
泛型
復制代碼的麻煩
我現在寫一個類,如下
public class StudyT
{
public void Add(string name)
{
Console.WriteLine("我是增加方法,變量是:" + name);
}
}
然后我可以實例化調用
StudyT<string> studyT = new StudyT<string>();
studyT.Add("許嵩");
但是我的Add方法,我希望string類型可以,int類型可以,float類型的也可以使用,那我怎么辦呢?
復制一下StudyT類,然后Add方法的參數類型改為int,這當然ok,但是麻煩
Object的裝箱拆箱損失性能
所以我選擇使用Object類型,如下
public class StudyT
{
public void Add(object name)
{
Console.WriteLine("我是增加方法,變量是:" + name);
}
}
非常好,Object是基類,這下我傳入int,string,float都可以用,但是又來了一個新問題,Object轉化的時候有裝箱拆箱,損失性能了,而且還有賦值不檢查類型的錯誤可能,所以,我選擇使用泛型
泛型的好處
public class StudyT<T>
{
public void Add(T name)
{
Console.WriteLine("我是增加方法,變量是:" + name);
}
}
泛型的使用方法就是
-
類后加
-
方法類型使用T表示
這下我實例化對象調用的時候,傳入什么類型,就是什么類型,而且還有類型檢查,很安全
泛型的約束
我這個方法啊,只希望某個類或者某個接口才能使用,你給我傳入一個int,string類型的沒用,所以我做個約束,你傳入的類型,必須是我想要的指定類型
public class StudyT<T> where T : IMovie
{
public void Add(T name)
{
Console.WriteLine("我是增加方法,變量是:" + name);
}
}
也很簡單,直接 where T : Movie 即可,表明,傳入的類型必須是繼承了IMovie接口的,不管是大電影,微電影,動畫片,科幻片啥的,只要繼承了IMovie接口就能使用
委托
我終於知道委托和事件是干嘛的了,多虧我同學寫的demo,不然我還是不理解委托
書上說委托可以解決大量if else的情況,百科也是這樣說的,但是我沒啥感覺,出了一個排序的例子,我沒感覺其他例子可以解決大量if else的,暫時不管這個了
對於委托最好的理解和使用就是發布訂閱模式了,在設計模式里面也稱之為觀察者模式
發布訂閱模式
這個例子很清楚的講解了委托的使用,我有3個類,服務器,客戶端,消息管理類,代碼如下
class Server
{
public void PublishInfo(string info)
{
Console.WriteLine($"服務器發布了新消息: {info}");
InformationManager.instance.Info = info;
InformationManager.instance.UpdateInformation?.Invoke();
}
}
class Client
{
string clientName = "";
public Client(string name,bool isSub = false)
{
clientName = name;
if (isSub)
{
InformationManager.instance.UpdateInformation += ReceiveInfo;
}
}
public void ReceiveInfo()
{
Console.WriteLine($"{clientName}用戶收到了消息: {InformationManager.instance.Info}");
}
}
class InformationManager
{
private string mInfo;
public Action UpdateInformation;
//單例的消息管理器實例
private static InformationManager _instance;
public static InformationManager instance
{
get
{
if (_instance == null)
{
_instance = new InformationManager();
}
return _instance;
}
}
}
然后Main方法調用如下
Server server = new Server();
Client client = new Client("劉備",true);
Client client1 = new Client("關羽");
Client client2 = new Client("張飛");
server.PublishInfo("好消息,許嵩發新歌啦");
結果很Nice,這就是委托了
不安全的委托
我們在給委托添加方法的時候,使用的是+=
InformationManager.instance.UpdateInformation += ReceiveInfo;
但是有時候我們會不小心寫成=,這樣訂閱者就會被覆蓋,我有3個訂閱者,結果寫成了=,只有第3個訂閱者收到消息了,前兩個被覆蓋了,這樣很不好.
不要說你會小心的,你不會忘記寫+=,這是無法避免的事情,因為我剛學的時候也總是忘記寫成=號,這就是不安全的委托,所以我們需要修改一下,使用事件解決這個問題
事件,就是安全的委托
事件:安全的委托
上面說了,委托方法的+=很容易被寫成=,這樣不安全,所以我們改一下代碼,使用事件,事件是安全的委托,因為事件強制你寫+=
class Server
{
public void PublishInfo(string info)
{
Console.WriteLine($"服務器發布了新消息: {info}");
InformationManager.instance.Info = info;
//InformationManager.instance.UpdateInformation?.Invoke(); 如果是委托需要調用
}
}
class Client
{
string clientName = "";
public Client(string name,bool isSub = false)
{
clientName = name;
if (isSub)
{
//這里的委托必須是+=,寫成=就覆蓋了,雖然我知道,但是我又忘了,所以寫成事件,事件強制+=,所以事件是安全的委托
InformationManager.instance.UpdateInformation += ReceiveInfo;
}
}
public void ReceiveInfo()
{
Console.WriteLine($"{clientName}用戶收到了消息: {InformationManager.instance.Info}");
}
}
class InformationManager
{
private string mInfo;
//public Action UpdateInformation; 委托
public event Action UpdateInformation;
//單例的消息管理器實例
private static InformationManager _instance;
public static InformationManager instance
{
get
{
if (_instance == null)
{
_instance = new InformationManager();
}
return _instance;
}
}
/// <summary>
/// 這個方法是事件的時候才用的,委托是直接調用,事件是觸發,所以觸發
/// </summary>
public string Info
{
get => mInfo;
set
{
if (value != mInfo)
{
mInfo = value;
if (UpdateInformation != null)
{
UpdateInformation();
}
}
}
}
}
委托需要調用,而事件是用來觸發的,所以在InformationManager加了一個觸發事件
這次再給委托添加方法的時候試試,必須寫成+=,這樣就再也不怕寫成=號了
反射
//反射第一種:GetType() 有實例對象,可以調用獲取屬性,方法,字段
DateTime dateTime = new DateTime();
Type type = dateTime.GetType();
PropertyInfo[] propertyInfos = type.GetProperties(); //所有的屬性
MethodInfo[] methodInfos = type.GetMethods(); //所有的方法
FieldInfo[] fieldInfos = type.GetFields(); //所有的字段
//反射第二種:typeof() 沒有實例對象的情況,比如靜態類或單純的類名
Type type1 = typeof(X);
PropertyInfo[] xpropertyInfos = type1.GetProperties(); //所有的屬性
MethodInfo[] xmethodInfos = type1.GetMethods(); //所有的方法
FieldInfo[] xfieldInfos = type1.GetFields(); //所有的字段
Type type2 = typeof(StudyThread);
MethodInfo[] tmethodInfos = type2.GetMethods(); //所有的方法
StudyThread studyThread = (StudyThread)Activator.CreateInstance(type2);//Activator是根據Type獲取實例對象
studyThread.Test();