/ csharp

csharp

c# 与 c++

  • C# 的开发工具叫 Visual C#
  • C++ 的开发工具叫 Visual C++
  • Visual C#Visual C++ 都在 Visual Studio

常用扩展:

  • .NET Reflector
  • PInvoke.net
  • ReSharp
  • MVVMLight

C# 继承自 C/C++ 的简洁明了的语法,这种语法简化了以前给程序员带来的一些问题。而且 C# 仍保持了 C++ 原有的功能,所以现在没有理由不从 C++ 转向 C#。

Q: Microsoft visual studio中配置管理器的目标平台anycpu 和x86有何区别?

当你需要连接一个32位的 native dll 的时候,你就不能编译成 anycpu,因为如果一旦按照64位启动,就跪。

所以一般来讲,只要你不去使用C++写的native dll,那就永远都anycpu

面向对象

对象(Object)是 C# 的一个工具,可以用来处理一组类似的事物。通过使用对象,Mike 只需要编写一次 Navigator 类,但在程序中可以根据需要使用多次。

使用类来建立对象

类对于对象来说就像是设计蓝图。如果你想在郊区房地产开发项目中盖 5 座同样的房子,肯定不会让设计师画 5 张同样的图纸。只需要一个蓝图就可以盖 5 个房子。

定义一个类时,就定义了它的方法,就像设计蓝图定义了房屋的布局一样。

可以使用同一个设计蓝图盖多个房子,同样地,可以使用一个类建立多个对象。

对象从类得到方法

一旦建立一个类,就可以根据需要使用 new 语句创建多个对象。创建对象后,类中的各个 public 方法都将成为对象的一部分。

由一个类创建的新对象,成为该类的一个实例

什么是方法?

方法就是一个有名字的代码块。

静态和非静态

静态和非静态方法的行为完全相同。唯一区别是,静态方法不要求有实例,而非静态方法需要先有一个实例。

静态成员先于实例成员被加载到内存中

只有创建对象,才有实例成员,因为静态类不能创建对象,所以静态类中只能存在静态成员。

因为静态成员先于实例成员被加载到内存中,所以非静态类可以有静态成员。

同样因为静态成员先于实例成员被加载到内存中,换句话说有静态成员的时候不一定有实例成员,所以我们使用类名调用静态成员,非静态成员通过对象调用。

静态成员调用:类名.静态成员名;

实例成员调用:实例名.实例成员;// 实例就是我们的对象

什么时候使用静态?

  • 作为工具类,例如 Console,调用频率高省去创建对象的繁琐步骤。
  • 在整个项目中资源共享,例如只输入一次密码,登录qq客户端后,我们可以直接访问空间、微博、邮箱都可以访问存储用户密码的静态成员实现鉴权访问。(只有当程序完全结束时,静态成员占用的资源才会被释放)

实例使用字段来跟踪状态

在类中增加字段很容易。只需要在所有方法外部声明变量。这样该类的每个实例都会有其自己的变量副本,用于记录各自的状态。

方法是对象什么,字段则是对象知道什么

对象的行为由方法定义,另外使用字段来跟踪其状态。

感谢内存

程序创建一个对象时,会放在计算机内存中的某一部分,称为堆(heap)。代码用 new 语句创建一个对象时,C# 会立即在堆中预留出空间,来存储该对象的数据。

封装

使用封装,将数据设置为 private,并增加方法对这些数据的访问加以保护。

private字段(Field)封装成属性(Property)。

封装意味着保证类中的一些数据是私有的。

使用封装来控制类方法和字段的访问。

私有字段和方法只能从类的内部访问。

封装可以是你的类:

  • 易于使用。
  • 易于维护。
  • 灵活。

类使用字段来跟踪其状态。另外,很多类都是用方法来保证这些字段反映最新状态。

封装是指让一个类对另一个类隐藏信息。这有助于避免程序中出 bug。

可以把对象认为是一个黑箱子。

考虑封装的几个注意:

  • 考虑字段可能以何种方式被滥用?如何设置不当,会出现什么问题?
  • 是不是勒种所有一切都是公开的?如果类中只有公共字段和方法,则可能需要多花一些时间来考虑封装问题。
  • 哪些字段需要在设置时做一些处理或计算?这些是最有可能需要封装的字段。如果有人以后写了一个方法来改变其中某个字段的值,则可能带来问题,导致你的程序无法完成工作。
  • 只将必要的字段和方法声明为 public。如果没有适当的原因,就不要把字段和方法声明为 public。不过也不要把一切都设置为 private。先花时间来考虑这个问题,哪些字段确实需要是 public 的,哪些不必。

封装保证数据干净。

Property(属性)使封装更容易。可以使用 property(属性),这些方法对其他对象来说就像是 field(字段)。可以用property(属性)来获取一个 baking field。

使用构造函数初始化私有字段。如果类有一个构造函数,用new语句创建对象时首先就会运行这个构造函数。

继承 —— 避免类中的重复代码

你有没有遇到这样的情况:一个对象它几乎能够完成你所希望它能完成的所有工作,只是还差那么一点点。你是不是威望只是稍做改动这个对象就能完美无缺?我们说继承是 C# 语言中最为强大的概念和技术之一。

通过继承,就能避免重复代码。类使用继承时,只需写一次代码。

建立模型:从一般到特殊。

使用继承,避免类中出现重复代码。

创建类层次体系。每个子类都扩展其基类。

使用冒号继承基类。子类从一个基类继承时,基类中所有字段、属性和方法都会自动增加到子类。

子类可以覆盖(override)它继承的 virtual 方法。

接口与抽象类——让类信守承诺

接口告诉类必须实现某些方法和属性。

使用 interface 定义接口。

面向对象程序设计的4大原则:程序员谈到 OOP 时,往往是指 4 个重要原则。继承、抽象、封装和多态。

异常

异常:语法上没有错误,在程序运行的过程中,由于某些原因程序出现了错误,不能再正常地运行

出现未处理异常。

// 如果想要你的程序更健壮一些,应该在程序中经常性的使用 try-catch 来进行遗产捕获
try
{
    // 可能会出现异常的代码
}
catch (Exception ex)
{
    ex.Data.Add("SomeKey", "SomeValue");
    throw;
}

由于变量作用域的限制,catch 中无法访问 在 try 内声明的变量,我们可以将变量在 try 之前声明并赋予默认值,然后在 trycatch 中使用。

当心构造函数中的异常!。构造函数没有返回值,甚至连 void 都没有。这是因为构造函数并不返回任何结果。它唯一的作用就是初始化一个对象,这就对于构造函数中的异常处理带来了一个问题。在构造函数中抛出异常时,视图实例化这个类的语句不会得到这个对象的实例。

委托(函数的占位符)

C# 中的委托可以理解为函数的一个包装,它使得 C# 中的函数可以作为参数来传递,这在作用上相当于 C++ 中的函数指针。

为什么要使用委托?

使用委托可以将一个方法传递给另一个方法,就像传递普通参数一样,这就是委托最大的作用。

有一些对字符串数组进行逐个处理的函数:

  • 转换为大写
  • 转换为小写
  • 加引号

可以把想要调用的函数作为参数传递,我传递哪个参数,它就调用哪个参数(方法)来对字符串或数组进行逐个处理。

Event

WebBrowser1.PreviewKeyDown 事件

WebBrowser1.PreviewKeyDown event fires twice

字符串

字符串转数字

int num1 = int.Parse("汉字111"); // 会报错
int num2;
int.TryParse("汉字111", out num2); // 不会报错

.NET 使用 Unicode 存储字符和文本

枚举和集合

通过枚举,可以用名称来表示数字。

可以使用数组来创建一副扑克牌。

泛型可以存储任何类型。

时间

计算两个指定时间的时间差:

DateTime t1 = DateTime.Parse("09:30:00");   
DateTime t2 = DateTime.Parse("10:30:00");  
  
System.TimeSpan t3 = t2 - t1;  

Console.WriteLine("倒计时:" + t3.TotalSeconds);

注释

/// 为代码段添加说明,可以自动生成文档

什么是进程?

每个正在操作系统上运行的应用程序都是一个 进程,一个进程可以包括一个或多个 线程,而线程是操作系统分配处理器时间的基本单元,在进程中可以有多个线程同时执行代码。

前台线程 与 后台线程

通过线程对象的 IsBackgroud 属性可以设置线程为前台线程后台线程。区别在于, 后台线程不会使托管程序处于运行状态,也就是说,如果进程 停止所有 前台线程,那么系统会认为此 进程 处于非运行状态,将会停止所有 后台线程 并关闭。

使用线程进程批量图像格式转换

Thread td = new Thread(new ThreadStart(ConvertImage)); // 通过线程调用 ConvertImage 方法进行转换
td.Start(); // 开启线程

终止:

if(td!=null) // 判断线程是否存在
{
    if(td.ThreadSate == ThreadState.Runing) // 判断线程是否正在运行
    {
        td.Abort(); // 终止线程
    }
}

使用 ThreadPool 创建线程。

在应用程序开发中,如果要做一些比较耗时的工作或启动其他应用程序,可以通过线程池创建一个新的线程来处理这些任务,这样可以防止因主线程繁忙出现“假死”现象。

ThreadPool.QueueUserWorkItem(()=>{
    // 处理 Word 文件或其他耗时操作
});

使用线程代替 Timer

Timer 可以按照用户定义的时间间隔来引发 Tick 事件,Tick 事件一般为周期性的,每隔若干秒或若干毫秒执行一次,Timer 工作在窗体线程中,如果 Timer 执行了较为耗时的操作,会增加窗体线程的负担,导致窗体中其他操作不能及时得到 CPU 资源,出现窗体短时间或长时间无响应的情况。在适当的情况下可以使用线程来代替 Timer,这样会减少窗体线程的负担。

适当地使用线程

在窗体应用程序开发过程中,应该尽量避免使用窗体线程做高强度的运算或 IO 操作。

如果窗体线程参与耗时的操作,会导致用户的操作不能及时分配到资源,用户界面会出现卡顿或无响应情况,所以线程是一个不错的选择。

多线程中存在的隐患

using System;
using System.Threading;
public class testStructure
{
   public static int tickets = 100;
   public static void Main(string[] args)
   {
     Thread thread1 = new Thread(SaleTicketThread1);
     Thread thread2 = new Thread(SaleTicketThread2);
     thread1.Start();
     thread2.Start();
     Thread.Sleep(4000);
   }
  
  private static void SaleTicketThread1()
  {
  	while(true){
    	if(tickets > 0){
          Console.WriteLine("线程1出票:"+ tickets--);
        }
      	else {
        	break;
        }
    }
  }
  
  private static void SaleTicketThread2()
  {
  	while(true){
    	if(tickets > 0){
          Console.WriteLine("线程2出票:"+ tickets--);
        }
      	else {
        	break;
        }
    }
  }
}

使用监视器对象实现线程同步

监视器对象(Monitor)能够确保线程拥有对共享资源的互斥访问,C# 通过 lock 关键字来提供简化的语法。

using System;
using System.Threading;
public class testStructure
{
   public static int tickets = 100;
  public static object globalObj = new object();// 辅助对象
   public static void Main(string[] args)
   {
     Thread thread1 = new Thread(SaleTicketThread1);
     Thread thread2 = new Thread(SaleTicketThread2);
     thread1.Start();
     thread2.Start();
   }
  
  private static void SaleTicketThread1()
  {
  	while(true){
      try{
        Monitor.Enter(globalObj);// 在 object 对象上获得排他锁
        Thread.Sleep(1);
      	if(tickets > 0){
          Console.WriteLine("线程1出票:"+ tickets--);
        }
      	else {
        	break;
        }
      }
      finally
      {
        Monitor.Exit(globalObj);// 在 object 对象上获得排他锁
      }
    }
  }
  
  private static void SaleTicketThread2()
  {
  	while(true){
      try{
        Monitor.Enter(globalObj);// 在 object 对象上获得排他锁
        Thread.Sleep(1);
      	if(tickets > 0){
          Console.WriteLine("线程2出票:"+ tickets--);
        }
      	else {
        	break;
        }
      }
      finally
      {
        Monitor.Exit(globalObj);// 在 object 对象上获得排他锁
      }
    }
  }
}

正则表达式

public bool isTelephone(string str_telephone)
{
    return System.Text.RegularExpressions.Regex.IsMatch(str_telephone, @"^(\d{3,4}-)?\d(6,8}$");
}

public bool isMobile(string str_mobile)
{
    return System.Text.RegularExpressions.Regex.IsMatch(str_telephone, @"^[1][3-8]\d{9}$");
}

启动和关闭 Timer 的两种方法

启动 Timer 时,可以将其 Enabled 属性设置为 true, 或者调用其 Start 方法;而关闭时,则需要将其 Enabled 属性设置为 false, 或者调用其 Stop 方法。

事件

事件通常包含 2 个参数,即,sender 事件源 和 包含事件数据的 e

ComboBox 控件

private void Frm_Main_Load(object sender, EventArgs e)
{
    cbox_Select.Items.Clear();
    cbox_Select.Items.Add("选项一");
    cbox_Select.Items.Add("选项二");
    // 设置默认选项
    cbox_Select.SelectedIndex = 0;
}

使用 try...catch 捕获异常

C# 绘制公章(p744)

c# 读写 ini 文件

[DllImport("kernel32")] private static extern int GetPrivateProfileString(string lpAppName,string lpKeyName,string lpDefault,StringBuilder lpReturnedString,int nSize,string lpFileName); 

[DllImport("kernel32")] private static extern long WritePrivateProfileString(string mpAppName,string mpKeyName,string mpDefault,string mpFileName); 

对 API 函数进行重写的实现代码如下:

#region 为 INI 文件中指定的节点取得字符串 
/// <summary> 
/// 为 INI 文件中指定的节点取得字符串 
/// </summary> 
/// <param name="lpAppName">欲在其中查找关键字的节点名称</param> 
/// <param name="lpKeyName">欲获取的项名</param> 
/// <param name="lpDefault">指定的项没有找到时返回的默认值</param> 
/// <param name="lpReturnedString">指定一个字符串缓冲区,长度至少为 nSize</param> 
/// <param name="nSize">指定装载到 lpReturnedString 缓冲区的最大字符数量</param> 
/// <param name="lpFileName">INI 文件名</param> 
/// <returns>复制到 lpReturnedString 缓冲区的字节数量,其中不包括那些 NULL 中止字符</returns> 
[DllImport("kernel32")] public static extern int GetPrivateProfileString(string lpAppName,string lpKeyName,string lpDefault,StringBuilder lpReturnedString,int nSize,string lpFileName); 
#endregion 
 
#region 修改 INI 文件中内容 
/// <summary> 
/// 修改 INI 文件中内容 
/// </summary> 
/// <param name="lpApplicationName">欲在其中写入的节点名称</param> 
/// <param name="lpKeyName">欲设置的项名</param> 
/// <param name="lpString">要写入的新字符串</param> 
/// <param name="lpFileName">INI 文件名</param> 
/// <returns>非零表示成功,零表示失败</returns> [DllImport("kernel32")] public static extern int WritePrivateProfileString(string lpApplicationName,string lpKeyName,string lpString,string lpFileName); 
#endregion 

程序剖析

每个C#程序的代码结构都几乎一致。所有程序都使用了命名空间、类、和方法,使代码更易于管理。

每次建立一个新程序时,要为它定义一个命名空间,使程序代码与 .NET Framework 类 区别开。

一个命名空间通常有多个类,当然简单的程序可能只有一个类。

每个类通常都只做一件特定的事情,这样一个复杂的程序就会由几个类组成。

我们写的每个程序都是为了解决一个问题,将问题分解,就可以建立不同的类。

使用类来建立对象

对象从类得到方法

一旦构建一个类,就可以根据需要,使用 new 语句创建多个对象。创建对象后,类中的各个 public 方法都将成为对象的一部分。

关于 static

static 的属性是某个群体才拥有的属性。

例如一个 Person 类,像 name, age 这些属性是每个人都有的。

但是像 人口总数人均年龄 就应该是 static 的,因为这些属性并不属于某个个体。

方法是对象什么,字段则是对象知道什么

程序创建一个新对象时,将增加到堆中

每种变量类型都像一个杯子,我们可以往这个杯子里加入水(数据),不同型号的杯子,决定了各自的容积。

  • short,16, 中杯
  • int ,32,大杯
  • long,64,特大杯
  • byte,8,是小杯。

当我们创建一个对象时,就会存放在计算机内存中的某一部分,成为(heap)。代码用 new 语句创建一个对象时,C# 会立即在堆中预留出空间,用来存储对象的数据。

属性 还是 字段

使用构造函数初始化 private 字段

如果需要初始化对象,但其中有些字段是 private 的,那么就不能使用对象初始化方法。幸运的是,任何类都可以增加一个特殊的方法,成为构造函数(constructor)。如果类有一个构造函数,用 new 语句创建对象时,首先运行的就是这个构造函数

变量就像数据外卖杯

所有数据都会占用内存中的一定空间,所以我们必须考虑:程序中使用一个字符串或一个数字时需要多少空间。这正是使用变量的原因之一。利用变量,可以在内存中预留足够的空间来存储数据。(三流程序对变量的欸型把握不够精准,总是会浪费内存)

调试

  • F9 添加断点

  • F10 不进方法逐行执行

  • F11 进方法逐行执行

  • Windows7 系统事件查看器:AppCrashView

  • Windows10 系统事件查看器:eventvwr

#if DEBUG
    if(IsInDesignMode)
    {
        UserFullName = "Bruce Lee";
    }
#endif

崩溃时的stacktrace,Windows会自动帮你记录,在eventvwr.msc中可以找到。

Visual Studio 2017

未能正确加载“ReferenceManagerPackage”包。
此问题可能是因配置更改或安装另一个扩展导致的。可通过查看文件“C:\Users\Administrator\AppData\Roaming\Microsoft\VisualStudio\15.0_3dfc125b\ActivityLog.xml”来获取详细信息。
重新启动 Visual Studio 可帮助解决此问题。
继续显示此错误消息吗?

以管理员身份运行

C:\Windows\System32>cd C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\PublicAssemblies

C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\PublicAssemblies>gacutil -i Microsoft.VisualStudio.Shell.Interop.11.0.dll
Microsoft (R) .NET Global Assembly Cache Utility.  Version 4.0.30319.0
版权所有(C) Microsoft Corporation。保留所有权利。

程序集已成功添加到缓存中

C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\PublicAssemblies>

bugs

GDI+ 中发生一般性错误。

  • 一般情况下是由于调用System.Drawing.Image.Save时出现的错误。
  • 原因一般是写入文件时,.net没有该目录的写入权限。
  • 解决方案:增加iis(对aspx而言)对该目录的写入权限

日常记录

C#调用dll提示"试图加载格式不正确的程序"原因及解决方法

试图加载格式不正确的程序

程序在32位操作系统上运行正常,在64位操作系统上运行读卡功能提示”试图加载格式不正确“。

出错原因:因为'任何 CPU'编译运行的程序在64位的机器上就会用运行为64位,而64位程序是不能加载32位dll的

解决方法:项目右键属性->项目设计器->生成->平台->把'默认设置(任何 CPU)'改为x86。

引用 dll

  • 如果 dll 是托管的,即是 c# vb 等编写的,可以直接引用。再使用USING XXXX; 引用命名空间就可以了。
  • 如果 dll 是非托管的,例如是 c++ 编写的,则需要使用额外的代码来使用 dll