(注明:文章内容都是本人在阅读c#相关文章作的一些笔记,会比较杂,因为我只是把我觉得有必要记下的记下了而已,而部分内容有些是直接在网站摘取某些是由本人语言组织的,内容纯粹是一个读书笔记记录)
C#语言是一种.Net语言,它的代码经过编译后产生的各种对象大多都是受.Net框架管理的托管代码。与C++不同的是它的内存管理是由.Net框架提供,而不像C++要自己管理。.Net框架使用垃圾收集器和引用来管理内存的使用。当一个对象不再被任何对象引用时,就可能被垃圾收集器清理掉,同时释放占用的内存。接着.Net框架会压缩托管堆以便在下次分配内存时有连续的内存块可供分配。这样一种处理方式是有效和安全的。
实际上C#中的引用就是一个指针,它的内容就是它所引用对象的地址。只不过在没有特殊声明的情况下,
C#的引用属于.Net托管的安全的指针。而且C#的引用语法不允许直接访问引用所包含的变量地址,这样就失去了C++中直接对指针操作的灵活性和技巧。C#的引用主要提供了易用和安全的方式访问内存,能够防止用户无意中执行某些破坏内存内容的操作。但如果确实需要使用指针直接访问内存,那么这段代码将被视作不安全代码,需要特别标注。标注不安全代码的语法如下:
unsafe int functionName() {...} // 把函数标记为不安全代码
unsafe class className {...} // 把类标记为不安全代码
class className{ unsafe int * pX; } // 把类的成员标记为不安全代码
但是不能把局部变量标记为不安全代码,例如:
int functionName() { unsafe int * pX; } // 这是错误的声明
在c# 2.0之前声明委托只有命名方法这么一种方法,使用命名方法构造的委托可以封装静态方法或实例方法,如下demoOne:
public delegate void Add(int x, int y);
public calss Test
{
public void TestAdd(int i, int y)
{
//******************
}
}
public calss delUsage
{
Test test=new Test();
Add a=test.TestAdd;
}
C# 2.0 允许您对委托进行实例化,并立即指定委托将在被调用时处理的代码块,就是使用匿名方法,代码如下demotwo:
// Create a delegate instance
delegate void Del(int x);
// Instantiate the delegate using an anonymous method
//注意此步不必创建单独的方法,因此减少了实例化委托所需的编码系统开销。比起demoOne省了实例化
Del d = delegate(int k) { /* ... */ };
例如,如果创建方法所需的系统开销是不必要的,在委托的位置指定代码块就非常有用。启动新线程即是一个很好的示例。无需为委托创建更多方法,线程类即可创建一个线程并且包含该线程执行的代码。如下demo3:
//如果按照常规方法创建一个线程对象,然后在另外创建一个方法,然后再把方法赋予创建线程作为其参数,现在使用匿名方法,可以省起创建方法所需的系统开销
void StartThread()
{
System.Threading.Thread t1 = new System.Threading.Thread
(delegate()
{
System.Console.Write("Hello, ");
System.Console.WriteLine("World!");
});
t1.Start();
}
如果局部变量和参数的范围包含匿名方法声明,则该局部变量和参数称为该匿名方法的外部变量或捕获变量。例如,下面代码段中的 n 即是一个外部变量:
int n = 0;
Del d = delegate() { System.Console.WriteLine("Copy #:{0}", ++n); };
与局部变量不同,外部变量的生命周期一直持续到引用该匿名方法的委托符合垃圾回收的条件为止。匿名方法不能访问外部范围的 ref 或 out 参数。匿名方法使用,一个比较好的代码例子:
// Declare a delegate
delegate void Printer(string s);
class TestClass
{
static void Main()
{
// Instatiate the delegate type using an anonymous method:
Printer p = delegate(string j)
{
System.Console.WriteLine(j);
};
// Results from the anonymous delegate call:
p("The delegate using the anonymous method is called.");
// The delegate instantiation using a named method "DoWork":
p = new Printer(TestClass.DoWork);
// Results from the old style delegate call:
p("The delegate using the named method is called.");
}
// The method associated with the named delegate:
static void DoWork(string k)
{
System.Console.WriteLine(k);
}
}
在 C# 中,结构类似于一个轻量类;它是一种堆栈分配的类型,可以实现接口,但不支持继承。
C# 程序在 .NET Framework 上运行,它是 Windows 的一个必要组件,包括一个称为公共语言运行时 (CLR) 的虚拟执行系统和一组统一的类库。CLR 是 Microsoft 的公共语言基础结构 (CLI) 的一个商业实现。CLI 是一种国际标准,是用于创建语言和库在其中无缝协同工作的执行和开发环境的基础。
用 C# 编写的源代码被编译为一种符合 CLI 规范的中间语言 (IL)。IL 代码与资源(如位图和字符串)一起作为一种称为程序集的可执行文件存储在磁盘上,通常具有的扩展名为 .exe 或 .dll。程序集包含清单,它提供关于程序集的类型、版本、区域性和安全要求等信息。执行 C# 程序时,程序集将加载到 CLR 中,这可能会根据清单中的信息执行不同的操作。然后,如果符合安全要求,CLR 执行实时 (JIT) 编译以将 IL 代码转换为本机机器指令。CLR 还提供与自动垃圾回收、异常处理和资源管理有关的其他服务。由 CLR 执行的代码有时称为“托管代码”,它与编译为面向特定系统的本机机器语言的“非托管代码”相对应。下图演示了 C# 源代码文件、基类库、程序集和 CLR 的编译时与运行时的关系。
在 C# 中,一个命名空间除了可包含其他命名空间外,还可包含类、结构、接口、枚举、委托等类型。
在 C# 中,值类型存储值,分成两类:结构和枚举。而结构又分为:数值类型(整型、浮点型、decimal)、bool、用户自定义的结构。在 C# 中,应用类型,用来存储实际数据的应用,包括class、interface、object、string、delegate、数组等。将一个值类型变量赋给另一个值类型变量时,将复制包含的值。这与引用类型变量的赋值不同,引用类型变量的赋值只复制对对象的引用,而不复制对象本身。与引用类型不同,从值类型不可能派生出新的类型。但与引用类型相同的是,结构也可以实现接口。
类型的装箱转换过程其实是,在垃圾回收堆中,新建一个对象用来存储对值类型值副本的一个应用,所以新建的对象存储的只是值类型值的一个COPY,对新建对象的修改并不会影响原来值类型值。实例代码如下:
class TestBoxing
{
static void Main()
{
int i = 123;
object o = i; // implicit boxing
i = 456; // change the contents of i
System.Console.WriteLine("The value-type value = {0}", i);
System.Console.WriteLine("The object-type value = {0}", o);
}
}
output:
The value-type value = 456
The object-type value = 123
在c#当中,所有的数组其实都是以Array作为基类,所以Array的一些属性和方法同样可以为数组进行利用。同样访问数组时也可以利用foreach遍历访问,包括应用多维数组的访问。方法参数中的关键字params、ref、out。ref 关键字使参数按引用传递。其效果是,当控制权传递回调用方法时,在方法中对参数所做的任何更改都将反映在该变量中。若要使用 ref 参数,则方法定义和调用方法都必须显式使用 ref 关键字。尽管 ref 和 out 在运行时的处理方式不同,但它们在编译时的处理方式是相同的。因此,如果一个方法采用 ref 参数,而另一个方法采用 out 参数,则无法重载这两个方法。代码如下:
class RefExample
{
static void Method(ref int i)
{
i = 44;
}
static void Main()
{
int val = 0; //使用此参数之前都必需先将ref 参数初始化,而out参数则不需要。
Method(ref val);
// val is now 44
System.Console.writeLine(val);//将会输出为44
}
}
也就是说,当调用方法时,在方法中加入修改了ref参数的值,则这个值将会保留下来。而当没有ref关键字时,这个值还是保持原来数值。例如:
class RefExample
{
static void Method(int i)
{
i = 44;
}
static void Main()
{
int val = 0;
Method(ref val);
// val is now 44
System.Console.writeLine(val);//将会输出为0
}
}
其实加上ref感觉就好像将参数变成了一个应用类型似的,碰到过一条笔试题,如下:
class Class1 {
private string str = "Class1.str";
private int i = 0;
static void StringConvert(string str) {
str = "string being converted.";
}
static void StringConvert(Class1 c) {
c.str = "string being converted.";
}
static void Add(int i) {
i++;
}
static void AddWithRef(ref int i) {
i++;
}
static void Main() {
int i1 = 10;
int i2 = 20;
string str = "str";
Class1 c = new Class1();
Add(i1);
AddWithRef(ref i2);
Add(c.i); //类的其中某个成员作为参数,其类型依然值类型,不对改变保留
StringConvert(str);
StringConvert(c); //类作为参数,其实类本来就是一个应用类型,对类的成员的改变将保留
Console.WriteLine(i1);
Console.WriteLine(i2);
Console.WriteLine(c.i); //值并没有改变
Console.WriteLine(str);
Console.WriteLine(c.str); //值已经改变并保留下来了
}
}
output results:
10 21 0 str string being converted.
out 关键字会导致参数通过引用来传,这与 ref 关键字类似,不同之处在于 ref 要求变量必须在传递之前进行初始化递。