反射
Reflection对象用于在运行时获取类型信息。可以访问正在运行的程序的元数据的类在System.Reflection命名空间中。
System.Reflection命名空间包含允许获取有关应用程序的信息的类,并向应用程序动态添加类型,值和对象。
反射有以下应用:
- 它允许在运行时查看属性信息。
- 它允许检查装配中的各种类型并实例化这些类型。
- 它允许后期绑定到方法和属性。
- 它允许在运行时创建新的类型,可使用这些类型执行一些任务。
查看元数据
我们在上一章中提到使用反射可以查看属性信息。
需要初始化System.Reflection类的MemberInfo对象,以发现与类相关联的属性。可将目标类的对象定义为:
System.Reflection.MemberInfo info = typeof(MyClass);
以下程序演示如下:
using System;
[AttributeUsage(AttributeTargets.All)]
public class HelpAttribute : System.Attribute
{
public readonly string Url;
public string Topic // Topic is a named parameter
{
get
{
return topic;
}
set
{
topic = value;
}
}
public HelpAttribute(string url) // url is a positional parameter
{
this.Url = url;
}
private string topic;
}
[HelpAttribute("Information on the class MyClass")]
class MyClass
{
}
namespace AttributeAppl
{
class Program
{
static void Main(string[] args)
{
System.Reflection.MemberInfo info = typeof(MyClass);
object[] attributes = info.GetCustomAttributes(true);
for (int i = 0; i < attributes.Length; i++)
{
System.Console.WriteLine(attributes[i]);
}
Console.ReadKey();
}
}
}
当它被编译并运行时,它显示附加到类MyClass的自定义属性的名称:
HelpAttribute
例子
在这个例子中,我们使用上一章创建的DeBugInfo属性,并使用反射来读取Rectangle类中的元数据。
using System;
using System.Reflection;
namespace BugFixApplication
{
//a custom attribute BugFix to be
//assigned to a class and its members
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]
public class DeBugInfo : System.Attribute
{
private int bugNo;
private string developer;
private string lastReview;
public string message;
public DeBugInfo(int bg, string dev, string d)
{
this.bugNo = bg;
this.developer = dev;
this.lastReview = d;
}
public int BugNo
{
get
{
return bugNo;
}
}
public string Developer
{
get
{
return developer;
}
}
public string LastReview
{
get
{
return lastReview;
}
}
public string Message
{
get
{
return message;
}
set
{
message = value;
}
}
}
[DeBugInfo(45, "Sukida", "12/8/2017", Message = "Return type mismatch")]
[DeBugInfo(49, "Maxsu", "10/10/2017", Message = "Unused variable")]
class Rectangle
{
//member variables
protected double length;
protected double width;
public Rectangle(double l, double w)
{
length = l;
width = w;
}
[DeBugInfo(55, "Sukida", "19/10/2017", Message = "Return type mismatch")]
public double GetArea()
{
return length * width;
}
[DeBugInfo(56, "Sukida", "19/10/2017")]
public void Display()
{
Console.WriteLine("Length: {0}", length);
Console.WriteLine("Width: {0}", width);
Console.WriteLine("Area: {0}", GetArea());
}
}//end class Rectangle
class ExecuteRectangle
{
static void Main(string[] args)
{
Rectangle r = new Rectangle(14.5, 17.5);
r.Display();
Type type = typeof(Rectangle);
//iterating through the attribtues of the Rectangle class
foreach (Object attributes in type.GetCustomAttributes(false))
{
DeBugInfo dbi = (DeBugInfo)attributes;
if (null != dbi)
{
Console.WriteLine("Bug no: {0}", dbi.BugNo);
Console.WriteLine("Developer: {0}", dbi.Developer);
Console.WriteLine("Last Reviewed: {0}", dbi.LastReview);
Console.WriteLine("Remarks: {0}", dbi.Message);
}
}
//iterating through the method attribtues
foreach (MethodInfo m in type.GetMethods())
{
foreach (Attribute a in m.GetCustomAttributes(true))
{
DeBugInfo dbi = (DeBugInfo)a;
if (null != dbi)
{
Console.WriteLine("Bug no: {0}, for Method: {1}", dbi.BugNo, m.Name);
Console.WriteLine("Developer: {0}", dbi.Developer);
Console.WriteLine("Last Reviewed: {0}", dbi.LastReview);
Console.WriteLine("Remarks: {0}", dbi.Message);
}
}
}
Console.ReadLine();
}
}
}
当上述代码被编译并执行时,它产生以下结果:
Length: 14.5
Width: 17.5
Area: 253.75
Bug no: 45
Developer: Sukida
Last Reviewed: 12/8/2017
Remarks: Return type mismatch
Bug no: 49
Developer: Maxsu
Last Reviewed: 10/10/2017
Remarks: Unused variable
Bug no: 55, for Method: GetArea
Developer: Sukida
Last Reviewed: 19/10/2017
Remarks: Return type mismatch
Bug no: 56, for Method: Display
Developer: Sukida
Last Reviewed: 19/10/2017
Remarks:
属性(Properties)
属性(Properties)被命名为类,结构和接口的成员。类或结构中的成员变量或方法称为字段。 属性是字段的扩展,并使用相同的语法访问。它们使用访问器,通过这些访问器可以读取,写入或操作私有字段的值。
属性不指定存储位置。它们有读取,写入或计算其值的访问器。
例如,假设有一个名称为Student的类,其中包含年龄(age),名称(name)和代码(code)的私有字段。我们无法从类范围外直接访问这些字段,但是可以拥有访问这些私有字段的属性。
访问器
属性的访问器包含有助于获取(读取或计算)或设置(写入)属性的可执行语句。访问器声明可以包含get访问器和set访问器。例如:
// Declare a Code property of type string:
public string Code
{
get
{
return code;
}
set
{
code = value;
}
}
// Declare a Name property of type string:
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
// Declare a Age property of type int:
public int Age
{
get
{
return age;
}
set
{
age = value;
}
}
例子
以下示例演示了如何使用属性:
using System;
namespace yiibai
{
class Student
{
private string code = "N.A";
private string name = "not known";
private int age = 0;
// Declare a Code property of type string:
public string Code
{
get
{
return code;
}
set
{
code = value;
}
}
// Declare a Name property of type string:
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
// Declare a Age property of type int:
public int Age
{
get
{
return age;
}
set
{
age = value;
}
}
public override string ToString()
{
return "Code = " + Code +", Name = " + Name + ", Age = " + Age;
}
}
class ExampleDemo
{
public static void Main()
{
// Create a new Student object:
Student s = new Student();
// Setting code, name and the age of the student
s.Code = "10010";
s.Name = "Maxsu";
s.Age = 24;
Console.WriteLine("Student Info: {0}", s);
//let us increase age
s.Age += 1;
Console.WriteLine("Student Info: {0}", s);
Console.ReadKey();
}
}
}
当上述代码被编译并执行时,它产生以下结果:
Student Info: Code = 10010, Name = Maxsu, Age = 24
Student Info: Code = 10010, Name = Maxsu, Age = 25
抽象属性
抽象类可能有一个抽象属性,它应该在派生类中实现。以下程序说明了这一点:
using System;
namespace yiibai
{
public abstract class Person
{
public abstract string Name
{
get;
set;
}
public abstract int Age
{
get;
set;
}
}
class Student : Person
{
private string code = "N.A";
private string name = "N.A";
private int age = 0;
// Declare a Code property of type string:
public string Code
{
get
{
return code;
}
set
{
code = value;
}
}
// Declare a Name property of type string:
public override string Name
{
get
{
return name;
}
set
{
name = value;
}
}
// Declare a Age property of type int:
public override int Age
{
get
{
return age;
}
set
{
age = value;
}
}
public override string ToString()
{
return "Code = " + Code + ", Name = " + Name + ", Age = " + Age;
}
}
class ExampleDemo
{
public static void Main()
{
// Create a new Student object:
Student s = new Student();
// Setting code, name and the age of the student
s.Code = "1011";
s.Name = "Maxsu";
s.Age = 21;
Console.WriteLine("Student Info:- {0}", s);
//let us increase age
s.Age += 1;
Console.WriteLine("Student Info:- {0}", s);
Console.ReadKey();
}
}
}
当上述代码被编译并执行时,它产生以下结果:
Student Info:- Code = 1011, Name = Maxsu, Age = 21
Student Info:- Code = 1011, Name = Maxsu, Age = 22
索引器
索引器允许对象被索引,例如:数组。 当为类定义索引器时,此类与虚拟数组类似。可以使用数组访问运算符([])访问此类的实例。
语法
一维索引器的语法如下:
element-type this[int index]
{
// The get accessor.
get
{
// return the value specified by index
}
// The set accessor.
set
{
// set the value specified by index
}
}
索引器的使用
索引器的行为声明在某种程度上与属性相似。与属性类似,可以使用get和set访问器定义索引器。但是,属性返回或设置特定的数据成员,而索引器从对象实例返回或设置特定值。 换句话说,它将实例数据分解成较小的部分,并对每个部分进行索引,获取或设置每个部分。
定义属性涉及提供属性名称。索引器不是用名称定义的,而是使用这个引用对象实例的关键字。以下示例演示了以下概念:
using System;
namespace IndexerApplication
{
class IndexedNames
{
private string[] namelist = new string[size];
static public int size = 10;
public IndexedNames()
{
for (int i = 0; i < size; i++)
namelist[i] = "N. A.";
}
public string this[int index]
{
get
{
string tmp;
if (index >= 0 && index <= size - 1)
{
tmp = namelist[index];
}
else
{
tmp = "";
}
return (tmp);
}
set
{
if (index >= 0 && index <= size - 1)
{
namelist[index] = value;
}
}
}
static void Main(string[] args)
{
IndexedNames names = new IndexedNames();
names[0] = "Maxsu";
names[1] = "Sukida";
names[2] = "Mark";
names[3] = "Jame";
names[4] = "Davinder";
names[5] = "Lucy";
names[6] = "Lily";
for (int i = 0; i < IndexedNames.size; i++)
{
Console.WriteLine(names[i]);
}
Console.ReadKey();
}
}
}
当上述代码被编译并执行时,它产生以下结果:
Maxsu
Sukida
Mark
Jame
Davinder
Lucy
Lily
N. A.
N. A.
N. A.
重载索引器
索引器可以重载。索引器也可以声明为多个参数,每个参数可能是不同的类型。 索引不一定是整数。 C# 允许索引为其他类型,例如:字符串。
以下示例演示了重载的索引器:
using System;
namespace IndexerApplication
{
class IndexedNames
{
private string[] namelist = new string[size];
static public int size = 10;
public IndexedNames()
{
for (int i = 0; i < size; i++)
{
namelist[i] = "N. A.";
}
}
public string this[int index]
{
get
{
string tmp;
if (index >= 0 && index <= size - 1)
{
tmp = namelist[index];
}
else
{
tmp = "";
}
return (tmp);
}
set
{
if (index >= 0 && index <= size - 1)
{
namelist[index] = value;
}
}
}
public int this[string name]
{
get
{
int index = 0;
while (index < size)
{
if (namelist[index] == name)
{
return index;
}
index++;
}
return index;
}
}
static void Main(string[] args)
{
IndexedNames names = new IndexedNames();
names[0] = "Maxsu";
names[1] = "Richer";
names[2] = "Nuber";
names[3] = "DockJ";
names[4] = "Vadder";
names[5] = "Sukida";
names[6] = "Ruby";
//using the first indexer with int parameter
for (int i = 0; i < IndexedNames.size; i++)
{
Console.WriteLine(names[i]);
}
//using the second indexer with the string parameter
Console.WriteLine(names["Nuha"]);
Console.ReadKey();
}
}
}
当上述代码被编译并执行时,它产生以下结果:
Maxsu
Richer
Nuber
DockJ
Vadder
Sukida
Ruby
N. A.
N. A.
N. A.
10
委托
C# 委托类似于C语言或C++中函数的指针。委托是一个引用类型变量,它保存对方法的引用。 引用可以在运行时更改。
委托一般用于实现事件和回调方法。所有委托都隐式地从System.Delegate类派生。
声明委托
委托声明确定委托可引用的方法。委托可以引用一个方法,它具有与委托相同的签名。
例如,考虑下面一个委托:
public delegate int MyDelegate (string s);
上述委托可用于引用具有单个字符串参数并返回int类型变量的任何方法。
委托声明的语法是:
delegate <return type> <delegate-name> <parameter list>
实例化委托
当声明了一个委托类型后,必须要使用new关键字创建一个委托对象,并将其与特定的方法相关联。创建代理时,传递给新表达式的参数类似于方法调用,但不包含方法的参数。 例如:
public delegate void printString(string s);
...
printString ps1 = new printString(WriteToScreen);
printString ps2 = new printString(WriteToFile);
以下示例演示了可以用于引用取整数参数并返回整数值的方法委托的声明,实例化和使用。
using System;
delegate int NumberChanger(int n);
namespace DelegateAppl
{
class TestDelegate
{
static int num = 10;
public static int AddNum(int p)
{
num += p;
return num;
}
public static int MultNum(int q)
{
num *= q;
return num;
}
public static int getNum()
{
return num;
}
static void Main(string[] args)
{
//create delegate instances
NumberChanger nc1 = new NumberChanger(AddNum);
NumberChanger nc2 = new NumberChanger(MultNum);
//calling the methods using the delegate objects
nc1(25);
Console.WriteLine("Value of Num: {0}", getNum());
nc2(5);
Console.WriteLine("Value of Num: {0}", getNum());
Console.ReadKey();
}
}
}
当上述代码被编译并执行时,它产生以下结果:
Value of Num: 35
Value of Num: 175
委托组播
代理对象可以使用“+”运算符来组合。一个委托调用它由两个委托组成。只能组合相同类型的委托。“-”运算符可用于从组合委托中删除组件委托。
使用委托的这个属性,可以创建一个方法的调用列表,该方法将在调用委托时调用。这称为委托组播。以下程序演示了一个委托组播:
using System;
delegate int NumberChanger(int n);
namespace DelegateAppl
{
class TestDelegate
{
static int num = 100;
public static int AddNum(int p)
{
num += p;
return num;
}
public static int MultNum(int q)
{
num *= q;
return num;
}
public static int getNum()
{
return num;
}
static void Main(string[] args)
{
//create delegate instances
NumberChanger nc;
NumberChanger nc1 = new NumberChanger(AddNum);
NumberChanger nc2 = new NumberChanger(MultNum);
nc = nc1;
nc += nc2;
//calling multicast
nc(5);
Console.WriteLine("Value of Num: {0}", getNum());
Console.ReadKey();
}
}
}
当上述代码被编译并执行时,它产生以下结果:
Value of Num: 525
使用委托
以下示例演示了使用委托。委托printString可用于引用方法,该方法将字符串作为输入,并且不返回任何内容。
使用这个委托来调用两个方法,第一个将字符串打印到控制台,第二个打印到一个文件中:
using System;
using System.IO;
namespace DelegateAppl
{
class PrintString
{
static FileStream fs;
static StreamWriter sw;
// delegate declaration
public delegate void printString(string s);
// this method prints to the console
public static void WriteToScreen(string str)
{
Console.WriteLine("The String is: {0}", str);
}
//this method prints to a file
public static void WriteToFile(string s)
{
fs = new FileStream("c:\\message.txt",
FileMode.Append, FileAccess.Write);
sw = new StreamWriter(fs);
sw.WriteLine(s);
sw.Flush();
sw.Close();
fs.Close();
}
// this method takes the delegate as parameter and uses it to
// call the methods as required
public static void sendString(printString ps)
{
ps("Hello World");
}
static void Main(string[] args)
{
printString ps1 = new printString(WriteToScreen);
printString ps2 = new printString(WriteToFile);
sendString(ps1);
sendString(ps2);
Console.ReadKey();
}
}
}
当上述代码被编译并执行时,它产生以下结果:
The String is: Hello World
事件
事件是用户操作,例如:按键,点击,鼠标移动等,或者某些事件,例如:系统生成的通知。应用程序需要响应事件发生时。 例如,中断。事件用于进程间通信。
使用委托与事件
事件在类中声明和引发,并使用同一个类或其他类中的委托与事件处理程序相关联。包含事件的类用于发布事件。这被称为发布者类。接受这个事件的其他类被称为订阅者类。事件使用发布者订阅者模型。
发布者是一个包含事件和委托的定义的对象。事件委托关联也在此对象中定义。 发布者类对象调用该事件,并将其通知给其他对象。
订阅者是接受事件并提供事件处理程序的对象。发布者类中的委托调用订阅者类的方法(事件处理程序)。
声明事件
要在类中声明事件,首先必须声明事件的委托类型。 例如,
public delegate string MyDel(string str);
接下来,使用event关键字声明事件:
event MyDel MyEvent;
上面的代码定义了一个名称为BoilerLogHandler的委托和一个名称为BoilerEventLog的事件,它在引发时调用委托。
例子
using System;
namespace SampleApp {
public delegate string MyDel(string str);
class EventProgram {
event MyDel MyEvent;
public EventProgram() {
this.MyEvent += new MyDel(this.WelcomeUser);
}
public string WelcomeUser(string username) {
return "Welcome " + username;
}
static void Main(string[] args) {
EventProgram obj1 = new EventProgram();
string result = obj1.MyEvent("Yiibai Yiibai");
Console.WriteLine(result);
}
}
}
当上述代码被编译并执行时,它产生以下结果:
Welcome Yiibai Point
集合
集合类是用于数据存储和检索的专门类。这些类提供对堆栈,队列,列表和哈希表的支持。大多数集合类实现相同的接口。
集合类用于各种目的,例如动态分配内存和基于索引等访问项目列表。这些类创建Object类的对象集合,它是 C# 中所有数据类型的基类。
各种集合类及其用途
以下是System.Collection命名空间的各种常用类。点击以下链接查看它们的使用细节。
| 类 | 描述 | 用法 |
|---|---|---|
| ArrayList | 它表示可以单独编制索引的对象的有序集合。 | 它基本上是一个数组的替代。但是,与数组不同,它可以使用索引从指定位置的列表中添加和删除项目,并且数组自动自动调整大小。它还允许动态内存分配,添加,搜索和排序列表中的项目。 |
| Hashtable | 它使用一个键来访问集合中的元素。 | 当需要使用键访问元素时,可使用哈希表,可以识别有用的键值。哈希表中的每个项目都有一个键/值对。键用于访问集合中的项目。 |
| SortedList | 它使用键和索引来访问列表中的项目。 | 排序列表是数组和散列表的组合。 它包含可以使用键或索引访问的项目列表。如果使用索引访问项目,它是一个ArrayList,如果使用键访问项目,它是一个Hashtable。集合中的项总是按键值排序。 |
| Stack | 它表示一个后进先出的对象集合。 | 当需要后进先出的操作时,可以使用Stack对象。 当您在列表中添加项目时,称为推送,当删除该项目时,将其称为弹出。 |
| Queue | 它表示一个先进先出的对象集合。 | 当需要先进先出的容器访问时可使用Queue对象。 当在列表中添加一个项目时,它被称为入队,当你删除一个项目时,它叫做出队。 |
| BitArray | 它使用值1和0表示二进制表示的数组。 |
当需要存储位但不提前知道位数时使用它。可以通过使用从零开始的整数索引来从BitArray集合中访问项目。 |
ArrayList
它表示可以单独编制索引的对象的有序集合。它基本上是一个数组的替代品。 但是,与数组不同,可以使用索引来从列表中指定位置添加和删除项目,并且数组自动调整大小。 它还允许动态内存分配,添加,搜索和排序列表中的项目。
ArrayList类的方法和属性
下表列出了ArrayList类的一些常用属性:
| 属性 | 描述 |
|---|---|
| Capacity | 获取或设置ArrayList可以包含的元素数。 |
| Count | 获取ArrayList中实际包含的元素数。 |
| IsFixedSize | 获取一个值,指示ArrayList是否具有固定大小。 |
| IsReadOnly | 获取一个值,指示ArrayList是否为只读。 |
| Item | 获取或设置指定索引处的元素。 |
下表列出了ArrayList类的一些常用方法:
| 序号 | 方法 | 描述 |
|---|---|---|
| 1 | public virtual int Add(object value); |
将对象添加到ArrayList的末尾。 |
| 2 | public virtual void AddRange(ICollection c); |
将ICollection元素添加到ArrayList的末尾。 |
| 3 | public virtual void Clear(); |
从ArrayList中删除所有元素。 |
| 4 | public virtual bool Contains(object item); |
确定元素是否在ArrayList中。 |
| 5 | public virtual ArrayList GetRange(int index, int count); |
返回一个ArrayList,它表示源ArrayList中元素的一个子集。 |
| 6 | public virtual int IndexOf(object); |
返回ArrayList或其一部分中从零开始第一次出现值的索引。 |
| 7 | public virtual void Insert(int index, object value); |
在指定索引的ArrayList中插入一个元素。 |
| 8 | public virtual void InsertRange(int index, ICollection c); |
将集合的元素插入到指定索引的ArrayList中。 |
| 9 | public virtual void Remove(object obj); |
从ArrayList中删除指定第一次出现的对象。 |
| 10 | public virtual void RemoveAt(int index); |
删除ArrayList的指定索引处的元素。 |
| 11 | public virtual void RemoveRange(int index, int count); |
从ArrayList中删除一系列元素。 |
| 12 | public virtual void Reverse(); |
反转ArrayList中元素的顺序。 |
| 13 | public virtual void SetRange(int index, ICollection c); |
在ArrayList中的一系列元素上复制集合的元素。 |
| 14 | public virtual void Sort(); |
对ArrayList中的元素进行排序。 |
| 15 | public virtual void TrimToSize(); |
将容量设置为ArrayList中实际的元素数量。 |
例子
以下示例演示了上述概念:
using System;
using System.Collections;
namespace CollectionApplication
{
class Program
{
static void Main(string[] args)
{
ArrayList al = new ArrayList();
Console.WriteLine("Adding some numbers:");
al.Add(41);
al.Add(70);
al.Add(133);
al.Add(56);
al.Add(120);
al.Add(213);
al.Add(90);
al.Add(111);
Console.WriteLine("Capacity: {0} ", al.Capacity);
Console.WriteLine("Count: {0}", al.Count);
Console.Write("Content: ");
foreach (int i in al)
{
Console.Write(i + " ");
}
Console.WriteLine();
Console.Write("Sorted Content: ");
al.Sort();
foreach (int i in al)
{
Console.Write(i + " ");
}
Console.WriteLine();
Console.ReadKey();
}
}
}
当上述代码被编译并执行时,它产生以下结果:
Adding some numbers:
Capacity: 8
Count: 8
Content: 41 70 133 56 120 213 90 111
Sorted Content: 41 56 70 90 111 120 133 213
Hashtable
Hashtable类表示基于键的哈希码组织的键和值对的集合。它使用键来访问集合中的元素。
当需要使用键访问元素时,可使用哈希表来标识有用的键值。哈希表中的每个项目都有一个键/值对。键用于访问集合中的项目。
Hashtable类的方法和属性
下表列出了Hashtable类的一些常用属性:
| 属性 | 说明 |
|---|---|
| Count | 获取Hashtable中包含的键值对的数量。 |
| IsFixedSize | 获取一个值,指示Hashtable是否具有固定大小。 |
| IsReadOnly | 获取一个值,指示Hashtable是否为只读。 |
| Item | 获取或设置与指定键相关联的值。 |
| Keys | 获取Hashtable中包含ICollection的键。 |
| Values | 获取Hashtable中一个包含的ICollection的值。 |
下表列出了Hashtable类的一些常用方法:
| 序号 | 方法 | 描述 |
|---|---|---|
| 1 | public virtual void Add(object key, object value); |
将具有指定键和值的元素添加到Hashtable中。 |
| 2 | public virtual void Clear(); |
从Hashtable中删除所有元素。 |
| 3 | public virtual bool ContainsKey(object key); |
确定Hashtable是否包含指定的键。 |
| 4 | public virtual bool ContainsValue(object value); |
确定Hashtable是否包含指定值。 |
| 5 | public virtual void Remove(object key); |
从Hashtable中删除指定键的元素。 |
例子
以下示例演示了上面所述的概念:
using System;
using System.Collections;
namespace CollectionsApplication
{
class Program
{
static void Main(string[] args)
{
Hashtable ht = new Hashtable();
ht.Add("001", "Maxsu");
ht.Add("002", "Andy");
ht.Add("003", "Jame");
ht.Add("004", "Mausambe");
ht.Add("005", "Mr. Amlan");
ht.Add("006", "Mr. Arif");
ht.Add("007", "Ritesh");
ht.Add("008", "Sukida");
if (ht.ContainsValue("Nuha Ali"))
{
Console.WriteLine("This student name is already in the list");
}
else
{
ht.Add("018", "Yiibai");
}
// Get a collection of the keys.
ICollection key = ht.Keys;
foreach (string k in key)
{
Console.WriteLine(k + ": " + ht[k]);
}
Console.ReadKey();
}
}
}
当上述代码被编译并执行时,它产生以下结果:
006: Mr. Arif
007: Ritesh
008: Sukida
018: Yiibai
003: Jame
002: Andy
004: Mausambe
001: Maxsu
005: Mr. Amlan
SortedList
SortedList类表示按键排序的键值对的集合,可以通过键和索引访问。
排序列表是数组和散列表的组合。 它包含可以使用键或索引访问的项目列表。 如果您使用索引访问项目,它是一个ArrayList,如果使用键访问项目,它是一个Hashtable。项目的集合总是按键值排序。
SortedList类的方法和属性
下表列出了SortedList类的一些常用属性:
| 属性 | 描述 |
|---|---|
| Capacity | 获取或设置SortedList的容量。 |
| Count | 获取SortedList中包含的元素数量。 |
| IsFixedSize | 获取一个值,指示SortedList是否具有固定大小。 |
| IsReadOnly | 获取一个值,指示SortedList是否为只读。 |
| Item | 获取并设置与SortedList中的指定键相关联的值。 |
| Keys | 获取SortedList中的键。 |
| Values | 获取SortedList中的值。 |
下表列出了SortedList类的一些常用方法:
| 序号 | 方法 | 描述 |
|---|---|---|
| 1 | public virtual void Add(object key, object value); |
将具有指定键和值的元素添加到SortedList中。 |
| 2 | public virtual void Clear(); |
从SortedList中删除所有元素。 |
| 3 | public virtual bool ContainsKey(object key); |
确定SortedList是否包含指定的键。 |
| 4 | public virtual bool ContainsValue(object value); |
确定SortedList是否包含指定值。 |
| 5 | public virtual object GetByIndex(int index); |
获取SortedList的指定索引处的值。 |
| 6 | public virtual object GetKey(int index); |
获取SortedList的指定索引处的键。 |
| 7 | public virtual IList GetKeyList(); |
获取SortedList中的键。 |
| 8 | public virtual IList GetValueList(); |
获取SortedList中的值。 |
| 9 | public virtual int IndexOfKey(object key); |
返回SortedList中指定键从零开始的索引。 |
| 10 | public virtual int IndexOfValue(object value); |
返回SortedList中指定值从零开始第一次出现的索引。 |
| 11 | public virtual void Remove(object key); |
从SortedList中删除指定键的元素。 |
| 12 | public virtual void RemoveAt(int index); |
删除SortedList指定索引处的元素。 |
| 13 | public virtual void TrimToSize(); |
将容量设置为SortedList中实际的元素数量。 |
例子
以下示例演示了上述概念的使用:
using System;
using System.Collections;
namespace CollectionsApplication
{
class Program
{
static void Main(string[] args)
{
SortedList sl = new SortedList();
sl.Add("001", "Maxsu");
sl.Add("002", "Alibaba");
sl.Add("003", "Tencent");
sl.Add("004", "Google");
sl.Add("005", "Microsoft");
sl.Add("006", "Apple");
sl.Add("007", "Huawei");
if (sl.ContainsValue("Yiibai"))
{
Console.WriteLine("This student name is already in the list");
}
else
{
sl.Add("008", "Yiibai");
}
// get a collection of the keys.
ICollection key = sl.Keys;
foreach (string k in key)
{
Console.WriteLine(k + ": " + sl[k]);
}
}
}
}
当上述代码被编译并执行时,它产生以下结果:
001: Maxsu
002: Alibaba
003: Tencent
004: Google
005: Microsoft
006: Apple
007: Huawei
008: Yiibai
Stack
它表示一个后进先出的对象集合。当您需要后进先出的容器时,可以使用Stack类。当在列表中添加项目时,叫作推送项目,当删除项目时,叫作弹出。
Stack类的方法和属性
下表列出了Stack类的一些常用属性:
| 属性 | 说明 |
|---|---|
| Count | 获取堆栈中包含的元素数量。 |
下表列出了Stack类的一些常用方法:
| 序号 | 方法 | 描述 |
|---|---|---|
| 1 | public virtual void Clear(); |
从堆栈中删除所有元素 |
| 2 | public virtual bool Contains(object obj); |
确定元素是否在堆栈(Stack)中。 |
| 3 | public virtual object Peek(); |
返回堆栈顶部的对象,但不删除它。 |
| 4 | public virtual object Pop(); |
删除并返回堆栈顶部的对象。 |
| 5 | public virtual void Push(object obj); |
在Stack的顶部插入一个对象。 |
| 6 | public virtual object[] ToArray(); |
将堆栈复制到一个新的数组。 |
例子
以下示例中演示了上述Stack的用法:
using System;
using System.Collections;
namespace CollectionsApplication
{
class Program
{
static void Main(string[] args)
{
Stack st = new Stack();
st.Push('I');
st.Push('A');
st.Push('B');
st.Push('I');
st.Push('I');
st.Push('Y');
Console.WriteLine("Current stack: ");
foreach (char c in st)
{
Console.Write(c + " ");
}
Console.WriteLine();
st.Push('Y');
st.Push('M');
Console.WriteLine("The next poppable value in stack: {0}", st.Peek());
Console.WriteLine("Current stack: ");
foreach (char c in st)
{
Console.Write(c + " ");
}
Console.WriteLine();
Console.WriteLine("Removing values ");
st.Pop();
st.Pop();
st.Pop();
Console.WriteLine("Current stack: ");
foreach (char c in st)
{
Console.Write(c + " ");
}
}
}
}
当上述代码被编译并执行时,它产生以下结果:
Current stack:
Y I I B A I
The next poppable value in stack: M
Current stack:
M Y Y I I B A I
Removing values
Current stack:
I I B A I
Queue
队列表示一个先进先出的对象集合。当需要先进先出的容器访问时可使用队列(Queue)。当在列表中添加项目时,将称为入队,当删除项目时,它被称为出队。
Queue类的方法和属性
下表列出了Queue类的一些常用属性:
| 属性 | 说明 |
|---|---|
| Count | 获取队列中包含的元素数量。 |
下表列出了Queue类的一些常用方法:
| 序号 | 方法 | 描述 |
|---|---|---|
| 1 | public virtual void Clear(); |
清空队列,即从Queue中删除所有元素。 |
| 2 | public virtual bool Contains(object obj); |
确定元素是否在队列中 |
| 3 | public virtual object Dequeue(); |
删除并返回队列开头的对象。 |
| 4 | public virtual void Enqueue(object obj); |
将对象添加到队列的末尾。 |
| 5 | public virtual object[] ToArray(); |
将队列复制到新数组。 |
| 6 | public virtual void TrimToSize(); |
将容量设置为队列中实际的元素数量。 |
例子
以下示例演示了Stack的用法:
using System;
using System.Collections;
namespace CollectionsApplication
{
class Program
{
static void Main(string[] args)
{
Queue q = new Queue();
q.Enqueue('Y');
q.Enqueue('I');
q.Enqueue('I');
q.Enqueue('B');
q.Enqueue('A');
q.Enqueue('I');
Console.WriteLine("Current queue: ");
foreach (char c in q) Console.Write(c + " ");
Console.WriteLine();
q.Enqueue('.');
q.Enqueue('C');
q.Enqueue('O');
q.Enqueue('M');
Console.WriteLine("Current queue: ");
foreach (char c in q) Console.Write(c + " ");
Console.WriteLine();
Console.WriteLine("Removing some values ");
char ch = (char)q.Dequeue();
Console.WriteLine("The removed value: {0}", ch);
ch = (char)q.Dequeue();
Console.WriteLine("The removed value: {0}", ch);
Console.ReadKey();
}
}
}
当上述代码被编译并执行时,它产生以下结果:
Current queue:
Y I I B A I
Current queue:
Y I I B A I . C O M
Removing some values
The removed value: Y
The removed value: I
BitArray
BitArray类管理位值的紧凑数组,它们表示为Boolean,其中true表示位为开(1),而false表示位为关(0)。
当需要存储位但不提前知道位数时,可考虑使用BitArray类。可以通过使用从零开始的整数索引来访问BitArray集合中的项目。
BitArray类的方法和属性
下表列出了BitArray类的一些常用属性:
| 属性 | 说明 |
|---|---|
| Count | 获取BitArray中包含的元素数量。 |
| IsReadOnly | 获取一个值,指示BitArray是否为只读。 |
| Item | 获取或设置BitArray中特定位置的位的值。 |
| Length | 获取或设置BitArray中的元素数量。 |
下表列出了BitArray类的一些常用方法:
| 序号 | 方法 | 描述 |
|---|---|---|
| 1 | public BitArray And(BitArray value); |
对当前BitArray中的元素与指定的BitArray中的相应元素执行按位AND运算。 |
| 2 | public bool Get(int index); |
获取BitArray中特定位置的位的值。 |
| 3 | public BitArray Not(); |
反转当前BitArray中的所有位值,使设置为true的元素更改为false,并将值为false的元素更改为true。 |
| 4 | public BitArray Or(BitArray value); |
对当前BitArray中的元素对指定的BitArray中的相应元素执行按位OR运算。 |
| 5 | public void Set(int index, bool value); |
将BitArray中指定位置的位设置为指定值。 |
| 6 | public void SetAll(bool value); |
将BitArray中的所有位设置为指定值。 |
| 7 | public BitArray Xor(BitArray value); |
BitArray中的元素对指定的BitArray中的相应元素执行逐位OR操作。 |
例子
以下示例演示了BitArray的用法:
using System;
using System.Collections;
namespace CollectionsApplication
{
class Program
{
static void Main(string[] args)
{
//creating two bit arrays of size 8
BitArray ba1 = new BitArray(8);
BitArray ba2 = new BitArray(8);
byte[] a = { 60 };
byte[] b = { 13 };
//storing the values 60, and 13 into the bit arrays
ba1 = new BitArray(a);
ba2 = new BitArray(b);
//content of ba1
Console.WriteLine("Bit array ba1: 60");
for (int i = 0; i < ba1.Count; i++)
{
Console.Write("{0, -6} ", ba1[i]);
}
Console.WriteLine();
//content of ba2
Console.WriteLine("Bit array ba2: 13");
for (int i = 0; i < ba2.Count; i++)
{
Console.Write("{0, -6} ", ba2[i]);
}
Console.WriteLine();
BitArray ba3 = new BitArray(8);
ba3 = ba1.And(ba2);
//content of ba3
Console.WriteLine("Bit array ba3 after AND operation: 12");
for (int i = 0; i < ba3.Count; i++)
{
Console.Write("{0, -6} ", ba3[i]);
}
Console.WriteLine();
ba3 = ba1.Or(ba2);
//content of ba3
Console.WriteLine("Bit array ba3 after OR operation: 61");
for (int i = 0; i < ba3.Count; i++)
{
Console.Write("{0, -6} ", ba3[i]);
}
Console.WriteLine();
Console.ReadKey();
}
}
}
当上述代码被编译并执行时,它产生以下结果:
Bit array ba1: 60
False False True True True True False False
Bit array ba2: 13
True False True True False False False False
Bit array ba3 after AND operation: 12
False False True True False False False False
Bit array ba3 after OR operation: 61
True False True True False False False False
泛型
泛型允许延迟编程元素的类或方法的数据类型的规范,直到它在程序中实际使用时确定。 换句话说,泛型允许编写一个可以使用任何数据类型的类或方法。
为类或方法编写规范,使用数据类型的替代参数。当编译器遇到类的构造函数或方法的函数调用时,它会生成代码来处理特定的数据类型。看看下面一个简单的例子将有助于理解这个概念:
using System;
using System.Collections.Generic;
namespace GenericApplication
{
public class MyGenericArray<T>
{
private T[] array;
public MyGenericArray(int size)
{
array = new T[size + 1];
}
public T getItem(int index)
{
return array[index];
}
public void setItem(int index, T value)
{
array[index] = value;
}
}
class Tester
{
static void Main(string[] args)
{
//declaring an int array
MyGenericArray<int> intArray = new MyGenericArray<int>(5);
//setting values
for (int i = 0; i < 5; i++)
{
intArray.setItem(i, i * 10);
}
//retrieving the values
for (int i = 0; i < 5; i++)
{
Console.Write(intArray.getItem(i) + " ");
}
Console.WriteLine();
//declaring a character array
MyGenericArray<char> charArray = new MyGenericArray<char>(5);
//setting values
for (int i = 0; i < 5; i++)
{
charArray.setItem(i, (char)(i + 97));
}
//retrieving the values
for (int c = 0; c < 5; c++)
{
Console.Write(charArray.getItem(c) + " ");
}
Console.WriteLine();
Console.ReadKey();
}
}
}
当上述代码被编译并执行时,它产生以下结果:
0 10 20 30 40
a b c d e
泛型特征
泛型是一种通过以下方式丰富程序的技术:
- 它可以帮助开发者最大限度地实现代码重用,类型安全和性能。
- 创建通用集合类。
.NET Framework类库在System.Collections.Generic命名空间中包含几个新的通用集合类。开发者使用这些通用集合类,而不是System.Collections命名空间中的集合类。 - 创建自己的通用接口,类,方法,事件和委托。
- 可以创建限制为允许访问特定数据类型的方法的泛型类。
- 通过反射获取关于通用数据类型在运行时使用的类型的信息。
泛型方法
在前面的例子中,使用了一个泛型类; 可以声明一个类型参数的泛型方法。以下程序说明了以下概念:
using System;
using System.Collections.Generic;
namespace GenericMethodAppl
{
class Program
{
static void Swap<T>(ref T lhs, ref T rhs)
{
T temp;
temp = lhs;
lhs = rhs;
rhs = temp;
}
static void Main(string[] args)
{
int a, b;
char c, d;
a = 100;
b = 201;
c = 'Y';
d = 'B';
//display values before swap:
Console.WriteLine("Int values before calling swap:");
Console.WriteLine("a = {0}, b = {1}", a, b);
Console.WriteLine("Char values before calling swap:");
Console.WriteLine("c = {0}, d = {1}", c, d);
//call swap
Swap<int>(ref a, ref b);
Swap<char>(ref c, ref d);
//display values after swap:
Console.WriteLine("Int values after calling swap:");
Console.WriteLine("a = {0}, b = {1}", a, b);
Console.WriteLine("Char values after calling swap:");
Console.WriteLine("c = {0}, d = {1}", c, d);
Console.ReadKey();
}
}
}
当上述代码被编译并执行时,它产生以下结果:
Int values before calling swap:
a = 100, b = 201
Char values before calling swap:
c = Y, d = B
Int values after calling swap:
a = 201, b = 100
Char values after calling swap:
c = B, d = Y
泛型委托
可以使用类型参数定义一个泛型委托。 例如:
delegate T NumberChanger<T>(T n);
以下示例显示了如何使用此委托:
using System;
using System.Collections.Generic;
delegate T NumberChanger<T>(T n);
namespace GenericDelegateAppl
{
class TestDelegate
{
static int num = 101;
public static int AddNum(int p)
{
num += p;
return num;
}
public static int MultNum(int q)
{
num *= q;
return num;
}
public static int getNum()
{
return num;
}
static void Main(string[] args)
{
//create delegate instances
NumberChanger<int> nc1 = new NumberChanger<int>(AddNum);
NumberChanger<int> nc2 = new NumberChanger<int>(MultNum);
//calling the methods using the delegate objects
nc1(25);
Console.WriteLine("Value of Num: {0}", getNum());
nc2(5);
Console.WriteLine("Value of Num: {0}", getNum());
Console.ReadKey();
}
}
}
Value of Num: 126
Value of Num: 630
匿名方法
前面我们学习过,委托可用于引用任何与委托签名相同的方法。换句话说,可以调用可以由委托使用该委托对象引用的方法。
匿名方法提供了一种将代码块作为委托参数传递的技术。匿名方法是没有名称的方法,只有方法体。
不需要在匿名方法中指定返回类型; 它是从方法体中的return语句来推断的。
编写匿名方法
使用delegate关键字创建代理实例时,就可以声明匿名方法。 例如,
delegate void NumberChanger(int n);
...
NumberChanger nc = delegate(int x)
{
Console.WriteLine("Anonymous Method: {0}", x);
};
代码块Console.WriteLine("Anonymous Method: {0}", x);是匿名方法体。
代理可以使用匿名方法和命名方法以相同的方式调用,即通过将方法参数传递给委托对象。
例如,
nc(10);
示例
以下示例演示如何实现概念:
using System;
delegate void NumberChanger(int n);
namespace DelegateAppl
{
class TestDelegate
{
static int num = 10;
public static void AddNum(int p)
{
num += p;
Console.WriteLine("Named Method: {0}", num);
}
public static void MultNum(int q)
{
num *= q;
Console.WriteLine("Named Method: {0}", num);
}
public static int getNum()
{
return num;
}
static void Main(string[] args)
{
//create delegate instances using anonymous method
NumberChanger nc = delegate(int x)
{
Console.WriteLine("Anonymous Method: {0}", x);
};
//calling the delegate using the anonymous method
nc(10);
//instantiating the delegate using the named methods
nc = new NumberChanger(AddNum);
//calling the delegate using the named methods
nc(5);
//instantiating the delegate using another named methods
nc = new NumberChanger(MultNum);
//calling the delegate using the named methods
nc(2);
Console.ReadKey();
}
}
}
当上述代码被编译并执行时,它产生以下结果:
Anonymous Method: 10
Named Method: 15
Named Method: 30
不安全代码
C# 允许在代码块的函数中使用指针变量来标记不安全的修饰符。不安全代码或非托管代码是使用指针变量的代码块。
指针
指针是一个变量,其值是另一个变量的地址,即存储器位置的直接地址。 类似于任何变量或常量,要使用指针必须先声明指针,然后才能使用它来存储任何变量地址。
指针声明的一般形式是:
type *var-name;
以下是有效的指针声明:
int *ip; /* pointer to an integer */
double *dp; /* pointer to a double */
float *fp; /* pointer to a float */
char *ch /* pointer to a character */
以下示例说明如何在 C# 中使用指针,使用不安全的修饰符:
using System;
namespace UnsafeCodeApplication
{
class Program
{
static unsafe void Main(string[] args)
{
int var = 20;
int* p = &var;
Console.WriteLine("Data is: {0} ", var);
Console.WriteLine("Address is: {0}", (int)p);
Console.ReadKey();
}
}
}
当上述代码通过编译并执行后,会产生以下结果:
Data is: 20
Address is: 890153869
不必将整个方法声明为不安全,也可将代码的一部分声明为不安全。下一节中的示例显示了这一点。
使用指针检索数据值
可以使用ToString()方法检索由指针变量引用的位置处存储的数据。以下示例演示如下:
using System;
namespace UnsafeCodeApplication
{
class Program
{
public static void Main()
{
unsafe
{
int var = 20;
int* p = &var;
Console.WriteLine("Data is: {0} " , var);
Console.WriteLine("Data is: {0} " , p->ToString());
Console.WriteLine("Address is: {0} " , (int)p);
}
Console.ReadKey();
}
}
}
当编译和执行上述代码时,会产生以下结果:
Data is: 20
Data is: 20
Address is: 97122341
将指针作为参数传递给方法
可以将指针变量传递给方法作为参数。以下示例说明了这一点:
using System;
namespace UnsafeCodeApplication
{
class TestPointer
{
public unsafe void swap(int* p, int *q)
{
int temp = *p;
*p = *q;
*q = temp;
}
public unsafe static void Main()
{
TestPointer p = new TestPointer();
int var1 = 10;
int var2 = 20;
int* x = &var1;
int* y = &var2;
Console.WriteLine("Before Swap: var1:{0}, var2: {1}", var1, var2);
p.swap(x, y);
Console.WriteLine("After Swap: var1:{0}, var2: {1}", var1, var2);
Console.ReadKey();
}
}
}
当上述代码被编译并执行时,它产生以下结果:
Before Swap: var1: 10, var2: 20
After Swap: var1: 20, var2: 10
使用指针访问数组元素
在 C# 中,一个数组名称和一个与数组数据类型相同的数据类型的指针是不一样的变量类型。 例如,int * p和int [] p是两个不一样的类型。我们可以增加指针变量p,因为它在内存中不是固定的,而数组的地址是固定在内存中,所以不能增加。
因此,如果您需要使用指针变量来访问数组数据,如传统上在C语言或C++(请查阅:C指针)中所做的那样,则需要使用fixed关键字修复指针。
以下示例演示如下:
using System;
namespace UnsafeCodeApplication
{
class TestPointer
{
public unsafe static void Main()
{
int[] list = {10, 100, 200};
fixed(int *ptr = list)
/* let us have array address in pointer */
for ( int i = 0; i < 3; i++)
{
Console.WriteLine("Address of list[{0}]={1}",i,(int)(ptr + i));
Console.WriteLine("Value of list[{0}]={1}", i, *(ptr + i));
}
Console.ReadKey();
}
}
}
当编译和执行上述代码时,会产生以下结果:
Address of list[0] = 31627168
Value of list[0] = 10
Address of list[1] = 31627172
Value of list[1] = 100
Address of list[2] = 31627176
Value of list[2] = 200
编译不安全的代码
要编译不安全的代码,必须使用命令行编译器指定/unsafe命令行开关。
例如,要编译一个名称为prog1.cs的源代码中包含不安全代码,请从命令行中输入命令:
csc /unsafe prog1.cs
如果您使用的是Visual Studio IDE,则需要在项目属性中启用不安全的代码。
参考以下步骤:
- 通过双击解决方案资源管理器中的属性节点打开项目属性(project properties)。
- 单击构建(Build)选项卡。
- 选择“允许不安全代码(Allow unsafe code)”选项。


