C#寫代碼踩坑記錄(持續更新,歡迎大家把自己的坑留下,避免後面的人踩)

1.int/int 返回值是int,例如:

 static void Main(string[] args)
        {
            int a = 10;
            int b = 3;
            Console.WriteLine((a / b));//返回值是3
            Console.Read();
        }

想換回3.3怎麼辦?將b轉換成double:

 static void Main(string[] args)
        {
            int a = 10;
            int b = 3;
            Console.WriteLine((a / (double)b));//返回值是3.3333333
            Console.Read();
        }

2.Dapper使用事物時,必須先將Connection打開,否則會報錯:

 using (var connection = ConnectionFactory.GetConnection())
 {
         connection.Open();
         var trans = connection.BeginTransaction();
         connection.Execute( "insert into table1 values(@a,@b,@c)" ,obj1s, trans);
         trans.Commit();
 }

注意一定要connection.Open();如果不使用事物的話就無所謂了

3.class 與 struct的區別:class是引用類型,struct是值類型,區別如下代碼:

聲明一個類:

public class PersonClass
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public override string ToString()
        {
            return string.Format("Name:" + Name + ",Age:" + Age.ToString());
        }
    }

聲明一個結構:

public struct PersonStruct
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public override string ToString()
        {
            return string.Format("Name:" + Name + ",Age:" + Age.ToString());
        }
    }

測試代碼如下:
單個對象測試:

		static void Main(string[] args)
        {
            //pc2會隨着pc1的變化而變化,他們指向的是同一個內存
            Console.WriteLine("Class Test:");
            PersonClass pc1 = new PersonClass();
            pc1.Age = 1;
            PersonClass pc2 = pc1;
            Console.WriteLine(pc2);
            pc1.Age = 2;
            Console.WriteLine(pc2);

            //ps2並不會隨着ps1的變化而變化,他們指向的是兩塊不同的內存
            Console.WriteLine("Struct Test:");
            PersonStruct ps1 = new PersonStruct();
            ps1.Age = 1;
            PersonStruct ps2 = ps1;
            Console.WriteLine(ps2);
            ps1.Age = 2;
            Console.WriteLine(ps2);
            Console.Read();
        }

運行結果如下:
在這裏插入圖片描述
數組測試:(注意有坑

        static void Main(string[] args)
        {
            PersonStruct[] personStructs = new PersonStruct[2];
            for (int i = 0; i < personStructs.Length; i++)
            {
                personStructs[i].Age = i;
            }

            PersonClass[] personClasses = new PersonClass[2];
            for (int i = 0; i < personClasses.Length; i++)
            {
                personClasses[i].Age = i;//此處會報錯,因爲沒有實例化
            }
        }

運行結果如下:
在這裏插入圖片描述
聲明瞭一個容量爲2的數組,在沒有給數組的元素賦值時,數組元素爲類型的默認值
結構體是值類型,初始值爲結構中各個值爲初始狀態的對象,而類是引用類型,初始值爲null,這也就解釋了上面一樣的寫法,爲什麼類會報錯的原因,截圖如下:

在這裏插入圖片描述

4.UDP通訊:在ReceiveFrom調用前,要調用Bind()方法或者SendTo,否則會報沒有綁定本地終結點。調用SendTo方法時,如果沒有調用Bind()方法,會自動分配一個本地終結點,但是主動調用Bind要給一個確定的終結點,這個終結點可能會被其他程序正在使用。如下:

這種寫法會拋異常:

 var targetEP = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 12345);
 var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
 byte[] bytes = new byte[1024];
 EndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);
 int count = socket.ReceiveFrom(bytes, ref remoteEP);//會拋出沒有綁定本地終結點的異常

解決的辦法就是在調用ReceiveFrom之前調用一次SendTo或者Bind:

var targetEP = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 12345);
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.SendTo(Encoding.UTF8.GetBytes("hello,i am client"),targetEP);//會自動給socket分配一個本地終結點
byte[] bytes = new byte[1024];
EndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);
int count = socket.ReceiveFrom(bytes, ref remoteEP);

5.在For循環中,開啓線程要採用帶參數的,否則,後果如下:

在這裏插入圖片描述
正確的姿勢爲:
在這裏插入圖片描述

6.使用NPOI時,注意樣式(CellStyle)的實例不要太多,否則容易報錯。

讀取內容時,要讀取到最後一行,必須<= worksheet.LastRowNum:
在這裏插入圖片描述

7.關於C# Winform的:

設置窗體的this.ShowInTaskbar = false會觸發窗體上的子控件的Load事件,如下圖所示:
在這裏插入圖片描述
點擊按鈕,設置窗體的ShowTaskbar=false時,會再次觸發UserControl的Load事件,
再設置ShowTaskbar=true時,會再次觸發UserControl的Load事件,所以在Load裏寫代碼要小心,並不一定Load事件只會在窗體加載時觸發一次。
個人感覺是:當窗體的ShowTaskbar屬性變化時,會將窗體的子控件全部Load一遍

8.操作INI時,INI的路徑要使用絕對路徑,不能使用相對路徑,否則會寫入或者讀取不成功!

封裝一個操作INI的方法:

public class CommonMethod
    {
        static object lockObj = new object();
        [DllImport("kernel32")]
        private static extern long WritePrivateProfileString(string section, string key, string val, string filePath);
        [DllImport("kernel32")]
        private static extern int GetPrivateProfileString(string section, string key, string def, StringBuilder retVal, int size, string filePath);
        public static void IniWriteValue(string sectionName, string key, string value,string fileName)
        {
            WritePrivateProfileString(sectionName, key, value, fileName);
        }
        /// <summary>
        /// 讀取INI文件的內容,內容的默認長度爲1000
        /// </summary>
        /// <param name="section"></param>
        /// <param name="key"></param>
        /// <param name="fileName"></param>
        /// <param name="size"></param>
        /// <returns></returns>
        public static string IniReadValue(string section, string key, string fileName,int size=1000)
        {
            try
            {
                StringBuilder stringBuilder = new StringBuilder(size);
                GetPrivateProfileString(section, key, "", stringBuilder, size, fileName);
                return stringBuilder.ToString().Trim();               
            }
            catch (Exception e)
            {
                throw;
            }
            
        }
       
    }

使用:

string iniPath = "test.ini";
CommonMethod.IniWriteValue("Test", "Count", "3", iniPath);
Console.Read();

運行完,發現程序目錄沒有test.ini,也沒有寫入任何東西:

在這裏插入圖片描述
當把上面的代碼改爲:

string iniPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "test.ini");
CommonMethod.IniWriteValue("Test", "Count", "3", iniPath);
Console.Read();

運行,發現能寫進去:

在這裏插入圖片描述
綜上,INI操作時,INI的路徑一定要用絕對路徑,不要用相對路徑,這和操作txt文件不一樣!!

9.操作List的時候要注意以下兩點:

不能在Foreach的時候移除被遍歷集合的元素!!!;
最好不要在For循環的時候移除被循環集合的元素!!!;
經常看到別人寫的C#代碼,上來就是一個Try…Catch,然後Try裏做的事情就是:

try
{
	foreach(var i in list)
	{
		//dosomthing..
		list.Remove(i)
	}
}
catch()
{}

哪些場景需要遍歷集合的元素,然後移除呢?比如對設備進行發送指令,有輪詢設備狀態的指令,有手動復位的指令,有其他手動需要發送的指令,手動發送指令的時候,肯定這個時候不能發送輪詢狀態,這個時候手動發送的指令優先級最高,需要一個list來裝指令,手動的指令就放在list的最前面,然後一個線程去消費這個list。當另一個線程檢測到list有指令的時候就需要把指令取出來發送給設備,取出指令的過程:
遍歷集合-》獲取一個元素(一條指令)-》發送設備指令-》移除這個指令
這裏就涉及到遍歷移除操作,於是就有上面的代碼。
我的解決方法是:(雖然沒有更好的辦法)

var list_copy=list.ToList();//複製一個list
foreach(var i in list_copy)
{
	//dosomthing..
	list.Remove(i);
}

至於爲什麼最好不要在For循環的時候移除被循環集合的元素,大家可以試驗一下
參考這個文章的:https://blog.csdn.net/z15732621582/article/details/73896321?locationNum=10&fps=1

10.單例模式時,遍歷屬性調用ToString時需要注意,是否會造成無限遞歸。

請看如下有BUG的代碼:

class Program
    {
        static void Main(string[] args)
        {
            Person p = Person.Instance;
            p.Name = "Tony";
            p.Age = 111;
            Console.WriteLine(p);
            Console.ReadLine();
        }
    }
    class Person
    {
        private static object lockObj = new object();
        private static Person instance;
        private Person()
        { 
        }
        public int Age { get; set; }
        public string Name { get; set; }
        public static Person Instance
        {
            get 
            {
                if (instance == null)
                {
                    lock (lockObj)
                    {
                        if (instance == null)
                        {
                            instance = new Person();
                        }
                    }
                }
                return instance;
            }
        }
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            foreach (var prop in this.GetType().GetProperties())
            {
                sb.AppendFormat("{0}:{1};",prop.Name,prop.GetValue(this,null));
            }
            return sb.ToString();
        }
    }

運行效果如下:
在這裏插入圖片描述
爲什麼?
因爲this.GetType().GetProperties()會把Instance也獲取到,然後調用Instance的ToString方法,從而一直遞歸循環
要解決這種BUG有兩種方法。
1.獲取到是Instance時就不管:

public override string ToString()
{
    StringBuilder sb = new StringBuilder();
    foreach (var prop in this.GetType().GetProperties())
    {
        if (prop.Name == "Instance")
        {
            continue;//跳過Instance
        }
        sb.AppendFormat("{0}:{1};",prop.Name,prop.GetValue(this,null));
    }
    return sb.ToString();
}

方法2:我們觀察到Instance是靜態屬性,因此我們可以跟成員屬性進行區分:

public override string ToString()
{
    StringBuilder sb = new StringBuilder();
    foreach (var prop in this.GetType().GetProperties(System.Reflection.BindingFlags.Public|System.Reflection.BindingFlags.Instance))
    {              
        sb.AppendFormat("{0}:{1};",prop.Name,prop.GetValue(this,null));
    }
    return sb.ToString();
}

11 WPF使用MVVM模式,給界面的RatioButton賦值問題

如下:WPF界面
在這裏插入圖片描述
三個RatioButton在一個Groupbox中,它們是互斥的關係:

<GroupBox Grid.Column="0" Header="確認狀態">
                <StackPanel>
                    <RadioButton  Content="已確認"  Margin="0,5,0,0"
                             IsChecked="{Binding IsAck,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" >
                        <i:Interaction.Triggers>
                            <i:EventTrigger EventName="Checked">
                                <i:InvokeCommandAction Command="{Binding AckCheckedChangedCmd}" />
                            </i:EventTrigger>
                            <i:EventTrigger EventName="Unchecked">
                                <i:InvokeCommandAction Command="{Binding AckCheckedChangedCmd}" />
                            </i:EventTrigger>
                        </i:Interaction.Triggers>
                    </RadioButton>
                    <RadioButton Content="未確認" Margin="0,5,0,0" IsChecked="{Binding IsUnack, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
                        <i:Interaction.Triggers>
                            <i:EventTrigger EventName="Checked">
                                <i:InvokeCommandAction Command="{Binding AckCheckedChangedCmd}" />
                            </i:EventTrigger>
                            <i:EventTrigger EventName="Unchecked">
                                <i:InvokeCommandAction Command="{Binding AckCheckedChangedCmd}" />
                            </i:EventTrigger>
                        </i:Interaction.Triggers>
                    </RadioButton>
                    <RadioButton  Content="全部" Margin="0,5,0,0" IsChecked="{Binding IsAckAndUnack,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
                        <i:Interaction.Triggers>
                            <i:EventTrigger EventName="Checked">
                                <i:InvokeCommandAction Command="{Binding AckCheckedChangedCmd}" />
                            </i:EventTrigger>
                            <i:EventTrigger EventName="Unchecked">
                                <i:InvokeCommandAction Command="{Binding AckCheckedChangedCmd}" />
                            </i:EventTrigger>
                        </i:Interaction.Triggers>
                    </RadioButton>
                </StackPanel>
            </GroupBox>

現在我們想讓程序啓動時,自動選中第2個RatioButton,也就是這三個變量的值是互斥的,同一時刻只能有一個爲true,我只想賦值一個變量就想達到這個效果:

IsUnack=true;

如果把這一句加入到ViewModel的構造函數中,則不一定會起作用,因爲其他兩個變量IsAck和IsAckAndUnack的初始值也爲true,當ViewModel賦值到View的DataContext上時,由於界面互斥的關係,IsUnack/IsAck會被自動賦值爲false,IsAckAndUnack保持爲true,因爲IsAckAndUnack綁定的控件在最後面,爲了解決這個問題,我們只有在窗體加載完成後再賦值IsUnack=true.由於我們使用的是MVVM模式,我們將窗體的加載函數綁定到命令,在命令的處理函數中執行IsUnack=true:
MainWindow.xaml:

 <i:Interaction.Triggers>
        <i:EventTrigger EventName="Loaded">
            <i:InvokeCommandAction Command="{Binding WindowLoadCmd}"></i:InvokeCommandAction>
        </i:EventTrigger>
    </i:Interaction.Triggers>

MainWindowViewModel:

 private bool isAck=true;
 public bool IsAck
 {
     get { return isAck; }
     set { isAck = value;RaisePropertyChanged(nameof(IsAck)); }
 }
 private bool isUnack=true;
 public bool IsUnack
 {
     get { return isUnack; }
     set { isUnack = value;RaisePropertyChanged(nameof(IsUnack)); }
 }
 private bool isAckAndUnack = true;
 public bool IsAckAndUnack
 {
     get { return isAckAndUnack; }
     set { isAckAndUnack = value;RaisePropertyChanged(nameof(IsAckAndUnack)); }
 }
 public DelegateCommand WindowLoadCmd { get; set; }
 public MainWindowViewModel()
 {
      WindowLoadCmd = new DelegateCommand(WindowLoad);
 }
 private void WindowLoad()
 {
     IsUnack=true;
 }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章