Giới thiệu

Tổng quan

C# (C Sharp) là một ngôn ngữ lập trình hướng đối tượng được phát triển bởi Microsoft, là phần khởi đầu cho kế hoạch .NET của họ. Tên của ngôn ngữ bao gồm ký tự thăng theo Microsoft nhưng theo ECMA là C#, chỉ bao gồm dấu số thường. Microsoft phát triển C# dựa trên C++ và Java. C# được miêu tả là ngôn ngữ có được sự cân bằng giữa C++, Visual Basic, Delphi và Java.

C# được thiết kế chủ yếu bởi Anders Hejlsberg kiến trúc sư phần mềm nổi tiếng với các sản phẩm Turbo Pascal, Delphi, J++, WFC.

Phần mở rộng thường gặp: .cs

Mô hình lập trình: Multi-paradigm programming language:Lập trình cấu trúc,Imperative programming,Lập trình hướng đối tượng,Event-driven programming,The Task-based Asynchronous Pattern, Lập trình hàm, Generic programming, Reflection (khoa học máy tính), Concurrent computing.

Năm xuất hiện: 2000.

Thiết kế bởi: Anders Hejlsberg, Microsoft.

Phát triển bởi: Microsoft.

Turing đầy đủ: Yes.

Kiểm tra kiểu: Static typing, Dynamic typing, Type system#Strong and weak typing, Type system#Safely and unsafely typed systems,Nominal type system,Type inference.

Thực hiện: Microsoft Visual C Sharp, .NET Framework, Mono, DotGNU.

Thuộc loại ngôn ngữ: Cω, Spec Sharp,Polyphonic C Sharp.

Ảnh hưởng từ: C++, Eiffel (programming language), Java (ngôn ngữ lập trình), Modula-3, Object Pascal, ML (programming language), Icon (programming language), Haskell (ngôn ngữ lập trình), Cω, F Sharp (programming language).

Ảnh hưởng tới: D (programming language), Dart (ngôn ngữ lập trình), F Sharp (programming language), Java (ngôn ngữ lập trình), Kotlin (programming language), Monkey X, Nemerle, Swift (ngôn ngữ lập trình), Vala (programming language).

Đặc điểm ngôn ngữ

C#, theo một hướng nào đó, là ngôn ngữ lập trình phản ánh trực tiếp nhất đến .NET Framework mà tất cả các chương trình .NET chạy, và nó phụ thuộc mạnh mẽ vào Framework này. Mọi dữ liệu cơ sở đều là đối tượng, được cấp phát và hủy bỏ bởi trình dọn rác Garbage-Collector (GC), và nhiều kiểu trừu tượng khác chẳng hạn như class, delegate, interface, exception, v.v, phản ánh rõ ràng những đặc trưng của .NET runtime.

So sánh với C và C++ thì C# bị giới hạn và được nâng cao ở một vài đặc điểm nào đó, nhưng không bao gồm các giới hạn sau đây:

· Các con trỏ chỉ có thể được sử dụng trong chế độ không an toàn. Hầu hết các đối tượng được tham chiếu an toàn, và các phép tính đều được kiểm tra tràn bộ đệm. Các con trỏ chỉ được sử dụng để gọi các loại kiểu giá trị; còn những đối tượng thuộc bộ thu rác (garbage-collector) thì chỉ được gọi bằng cách tham chiếu.

· Các đối tượng không thể được giải phóng tường minh.

· Chỉ có đơn kế thừa, nhưng có thể cài đặt nhiều interface trừu tượng (abstract interfaces). Chức năng này làm đơn giản hóa sự thực thi của thời gian thực thi.

· C# thì an-toàn-kiểu (typesafe) hơn C++.

· Cú pháp khai báo mảng khác nhau("int[] a = new int[5]" thay vì "int a[5]").

· Kiểu thứ tự được thay thế bằng tên miền không gian (namespace).

· C# không có tiêu bản.

· Có thêm Properties, các phương pháp có thể gọi các Properties để truy cập dữ liệu.

· Có reflection.

C# 3.0, các đặc điểm mới

Trong C# 3.0, sẽ có vài bổ sung cơ bản sau:

· Các từ khóa "select, from, where" cho phép truy vấn từ một tập, từ SQL, v.v. (hay còn được gọi là LINQ - viết tắt của Language INtergrated Query)

· Khởi tạo đối tượng: Customer c=new Customer(); c.Name="James"; trở thành Customer c=new Customer{ Name="James" };

· Các biểu thức lambda: listOfFoo.Where(delegate(Foo x) {return x.size>10;}) trở thành listOfFoo.Where(x => x.size>10);

· var x = "hello"; có thể hoán đổi với string x = "hello";

· Các phương thức mở rộng

.NET Framework

.NET Framework là một thư viện class có thể được sử dụng với một ngôn ngữ .NET để thực thi các việc từ thao tác chuỗi cho đến phát sinh ra các trang web động (ASP.NET), phân tích XML và reflection. .NET Framework được tổ chức thành tập hợp các namespace, nhóm các class có cùng chức năng lại với nhau, thí dụ như System.Drawing cho đồ hoạ, System.Collections cho cấu trúc dữ liệu và System.Windows.Forms cho hệ thống Windows Forms.

Cấp cao hơn nữa được cung cấp bởi khái niệm này là assembly. Một assembly là một file hoặc nhiều file được liên kết với nhau (thông qua file al.exe), chứa đựng nhiều namespace và object. Các chương trình cần các lớp để thực thi một chức năng đặc biệt nào đó sẽ tham chiếu các assembly chẳng hạn như System.Drawing.dll và System.Windows.Forms.dll cũng như các core library (lưu trong file mscorlib.dll).

Thí dụ kinh điển Hello World

Dưới đây là một chương trình C# rất đơn giản, với thí dụ "Hello World" kinh điển:

public class ExampleClass

{
    static void Main(string[] args)
    {
        System.Console.WriteLine("Hello world!");
    }
}

Chương trình này sẽ viết chuỗi Hello World! ra màn hình console. Mỗi dòng code có một mục đích đặc biệt, chi tiết như sau:

public class ExampleClass

Đây là định nghĩa lớp public, nghĩa là các đối tượng trong project khác có thể tự do sử dụng lớp này. Tất cả thông tin trong dấu ngoặc móc mô tả về lớp này.

static void Main()

Đây là điểm vào của chương trình. Nó có thể được gọi từ đoạn code khác với cú pháp ExampleClass.Main().

Khả năng của C#

C# có thể dùng để tạo các ứng dụng console, đó là các ứng dụng thuần văn bản chạy trên DOS window. Hầu như bạn chỉ tạo các ứng dụng console khi cần kiểm tra các thư viện lớp, hoặc cho các tiến trình daemon Unix/Linux. Tất nhiên, bạn cũng có thể dùng C# để tạo các ứng dụng dùng cho các công nghệ tương thích .NET. Trong phần này, chúng ta xem qua về các kiểu ứng dụng khác nhau có thể tạo ra bằng C#.

Tạo các ứng dụng ASP.NET

ASP là một công nghệ của Microsoft dùng để tạo các trang web có nội dung động. Một trang ASP thực chất là một file HTML có nhúng các khối server-side VBScript hay JavaScript. Khi một trình duyệt khách yêu cầu một trang ASP page, web server sẽ sinh ra mã HTML, xử lí các server-side script khi chúng đến. Thường thì các script sẽ truy cập vào một cơ sở dữ liệu để lấy dữ liệu, và biểu diễn trên trang HTML. ASP là cách đơn giản nhất để tạo các ứng dụng browser-based.

ASP tất nhiên cũng có một vài hạn chế. Trước tiên, các trang ASP thỉnh thoảng trở nên rất chậm bởi vì mã  server-side được thông dịch thay vì đựơc biên dịch. Thứ hai, các file ASP khó bảo trì bởi vì chúng không có cấu trúc; mã server-side ASP và HTML được trộn lẫn với nhau. Thứ ba, ASP đôi khi kho phát triển bởi nó không quan tâm đến bẫy lỗi và kiểm tra kiểu. Cụ thể, nếu bạn dùng VBScript và muốn bẫy lỗi trên các trang của bạn, bạn phải dung câu lệnh On Error Resume Next, và cho phép tất cả các thành phần gọi thông qua một Err.Number để chắc rằng tất cả đều tốt.

ASP.NET  là một phiên bản mới của ASP đã cải tiến rất nhiều các thiếu xót của nó. Nó không chỉ thay thế ASP; hơn thế, các trang ASP.NET có thể sống chung với các ứng dụng ASP trên cùng một máy chủ. Tất nhiên bạn có thể lập trình ASP.NET với C#.

Các đặc tính của ASP.NET

Trước tiên, và có lẽ là quan trọng nhất, các trang ASP.NET là các trang có cấu trúc. Có nghĩa là mỗi trang là thực tế là một lớp được thừa kế từ lớp .NET System.Web.UI.Page, và có thể ghi đè một tập các phương thức sẽ dùng trong thời gian sống của trang web (bạn hãy tưởng tượng rằng nhữn sự kiện này như là anh em bà con với các sự kiện OnApplication_Start và OnSession_Start trong file global.asa của ASP cũ.) Bởi vì bạn có thể chuyển các thao tác của một trang thành các sự kiện sáng nghĩa hơn, chính vì thể mà các tramg ASP.NET dễ hiểu hơn.

Một điểm mạnh khác là các trang ASP.NET có thể được tạo trong VS.NET, cùng chung môi trường với các thành phần luận lí và dữ liệu sẽ được dùng trong các trang web này. Một nhóm đề án VS.NET, hoặc solution, chứa tất cả các file liên quan đển một ứng dụng. Hơn thế nữa bạn có thể bẫy lỗi các trang ASP của bạn ngay trong trình thiết kế; trước đây, thật là khó khăn để có thể cấu hình InterDev và các đề án web server để thực hiện bẫy lỗi.

Rõ ràng, đặc tính ASP.NET's code-behind giúp các bạn có thể dễ dàng cấu trúc một trang web. ASP.NET cho phép bạn tách biệt các chức năng server-side của trang thành một lớp, biên dịch lớp đó thành một DLL, và đặt DLL đó vào một thư mục bên dưới phần HTML. Một code-behind chi phối đỉnh của một trang web tương đương với file DLL của nó. Khi một trình duyệt yêu cầu trang, web server phát ra các sự kiện trong lớp của page's code-behind DLL.

Cuối cùng không kém phần quan trọng, ASP.NET thật sự đáng chú ý với khả năng tăng cường sự thực thi. Ngược lại với các trang ASP được thông dịch cho mỗi yêu cầu, web server lưu giữ lại các trang ASP.NET sau quá trình biên dịch. Nghĩa là các yêu cầu sau của một trang ASP.NET sẽ thực thi nhanh hơn trang đầu tiên.

ASP.NET dễ tạo các trang hơn bởi vì nó được chiếu bởi trình duyệt, bạnc có thể sử dụng một môi trường mạng intranet. Theo kinh nghiệm truyền thống thì một ứng dụng form-based thường là tốt hơn một user interface, nhưng cũng khó bảo trì hơn vì nó chạy trên nhiều máy khác nhau.

Với sự ra đời của Internet Explorer 5 và sự thực thi mơ hồ của Navigator 6, tất nhiên các đặc tính của ứng dụng form-based bị che mờ. IE 5's hỗ trợ nhất quán và mạnh mẽconsistent cho DHTML cho phép các nhà lập trình tạo các ứng dụng web-based đẹp là lớn hơn. Tất nhiên, các ứng dụng này bắt buộc phải theo chuẩn của IE và không được hỗ trợ bởi Navigator. Trong nhiều lĩnh vực công nghiệp, chuẩn này đã trở nên phổ biến.

Web Forms

Để dễ dàng cho việc tạo các trang có cấu truc, Visual Studio .NET cung cấp Web Forms. Chúng cho phép bạn tạo các trang ASP.NET sinh động như cách mà VB 6 hay C++ Builder windows đã làm; nó cách khác, bằng cách kéo các controls từ toolbox vào form, sau đó sắp xếp cho đẹp, điền mã quản lí sự kiện thích hợp vào control đó. Khi bạn dung C# để tạo các Web Form, bạn đang tạo một lớp C# được thừa kế từ lớp Page base, và một trang ASP được chỉ định như là code-behind. Tất nhiên, không bắt buộc phải dùng C# để tạo một Web Form; bạn có thể dùng VB.NET hoặc một ngôn ngữ biết .NET khác.

In the past, the difficulty of web development has discouraged some teams from attempting it. To succeed in web development, you had to know so many different technologies, such as VBScript, ASP, DHTML, JavaScript, and so on. By applying the Form concepts to web pages, Web Forms promise to make web development easier. Only time will tell, however, how successful Web Forms and Web Controls (which we'll look at next) will be at insulating the developer from the complexities of web design.

Web Controls

Các control thường được cư trú trên một Web Form không phải là các ActiveX control. Hơn nữa, chúng là XML tags trong ASP namespace và  browser có thể chuyển sang HTML và client-side script khi một trang được yêu cầu. Đặc biệt hơn, web server có thể các điều khiển server-side control theo nhiều cách khác nhau, sinh ra sự biến đổi phù hợp với các yêu cầu của các web browser riêng biệt. Điều này có nghĩa là sẽ dễ dàng viết các giao diện người dùng tinh vi cho các trang web, đừng bận tâm đến vấn đề tương thích trình duyệt web– bởi vì Web Forms sẽ làm điều đó cho bạn.

Bạnc có thể dùng C# hay VB.NET để mở rộng hộp công cụ Web Form. Việc tạo một server-side control mới đơn giản là thực thi lớp .NET System.Web.UI.WebControls.WebControl.

Web Services

Ngày nay, các trang HTML là nguyên nhân của hầu hết các xung đột trên World Wide Web. Với XML, các máy vi tính có một định dạng device-independent để dùng cho việc truyền thông với các máy khác trên mạng Web. Trong tương lại, các máy tính có thể sẽ dùng Web và XML để trao đổi thông tin hơn là dùng các line chuyên dụng và theo những định dạng riêng như EDI (Electronic Data Interchange). Các Web Service được thiết kế cho một web hướng dịch vụ, trong đó các máy tính ở xa cung cấp cho nhau các thông tin động có thể phân tích và tái định dạng, trước khi trao lại cho người dùng. Một Web Service là cách đơn giản nhất để một máy tính có thể cung cấp thông tin cho các máy tính khác trên Web dưới định dạng XML.

Về mặt kĩ thuật, một Web Service trong .NET là một trang ASP.NET theo định dạng XML thay vì theo định dạng HTML để yêu cầu các client. Các trang này có một code-behind DLL chứa một lớp xuất phát từ lớp WebService. VS.NET IDE cung cấp một cơ chế để tiện cho việc phát triển Web Service.

Có hai lí do chính để một tổ chức chọn Web Services. Lí do thứ nhất là bởi vì chúng đáng tin cậy trên HTTP, Web Services có thể dùng các mạng có sẵn (the Web) như một môi trường cho việc truyền thông. Một lí do khác là bởi vì các Web Service dùng XML, một định dạng dữ liệu tự mô tả, mang tính phổ biến, và độc lập nền.

Tạo các Windows Form

Mặc dù C# và .NET được thiết kế để phát triển web, nhưng chúng vẫn hỗ trợ mạnh mẽ cho cái gọi là ứng dụng "fat client", các ứng dụng có thể được cài đặt trên một máy người dùng cuối. Hỗ trợ này gọi là Windows Forms.

Một Windows Form là câu trả lời của .NET cho VB 6 Form. Dùng để thiết kế một giao diên window sinh động, bạn chỉ đơn giản kéo các control từ vào trên Windows Form. Để xác định cách xử của window, bạn viết các thủ tục quản lí sự kiện cho form controls.  Một đề án Windows Form được dịch thành một EXE phải được cài đặt trong một môi trường ở máy tính người dùng cuối. Giống như các kiểu đề án .NET khác, đề án Windows Form được hỗ trợ cho cả VB.NET và C#.

Windows Controls

Mặc dù Web Forms và Windows Forms được phát triển theo cùng một cách, bạn dùng các loại khác nhau của controls để định vị chúng. Web Forms dùng Web Controls, và Windows Forms dùng Windows Controls.

Một Windows Control là một ActiveX control. Đằng sau sự thực thi của một Window control, là sự biên dich sang một DLL để có thể cài đặt trên máy khách. Thật vậy, .NET SDK cung cấp một tiện ích dùng để tạo một vỏ bọc cho các ActiveX control, vì thể chúng có thể được đặt trong Windows Forms. Giống trường hợp này các Web Control, Windows Control được tạo thành từ một lớp khác System.Windows.Forms.Control.

Windows Services

Một Windows Service là một chương trình được thiết kế để chạy trên nền Windows NT/2000/XP (không hỗ trợ trên Windows 9x). Các dịch vụ này rất hữu ích khi bạn muốn một chương trình có thể chạy liên tục và sẵn sàng đáp ứng các sự kiện mà không cần người dùng phải khởi động. Ví dụ như một World Wide Web Service ở trên các web server luôn lắng nghe các yêu cầu từ trình khách.

Thật dễ dàng để viết các dịch vụ trong C#. Với thư viện lớp cơ sở .NET Framework sẵn có trong không gian tên System.ServiceProcess namespace chuyên dùng để tổ chức các tác vụ boilerplate kết hợp với các dịch vụ, ngoài ra, Visual Studio .NET cho phép bạn tạo một đề án C# Windows Service, với các mã nguồn cơ bản ban đầu.

Quy tắc chung

C# là ngôn ngữ lập trình hướng đối tượng (Object Oriented Programming - OOP).

Tất cả các từ khoá của C# đều bao gồm các ký tự thường (không chứa ký tự in hoa).

C phân biệt ký hoa và ký tự thường, ví dụ A và a là khác nhau.

Một chương trình C# thường được chia thành các lớp, trong mỗi lớp có các thuộc tính và phương thức. Trong một lớp nào đó của chương trình phải chứa phương thức Main().

Số 1 được hiểu là số nguyên, nhưng số 1.0 chẳng hạn thì lại được hiểu là số thực.

Khối lệnh được đặt trong cặp ({}).

Kết thúc câu lệnh phải có dấu (;).

C# hỗ trợ các loại chú thích sau:

+ Chú thích trên một dòng: dùng cặp //

+ Chú thích trên nhiều dòng: dùng cặp /*...*/

+ Chú thích cho XML: ///

Cấu trúc cơ bản của một chương trình C# (Console Application):

using System;

namespace Tên_namespace

{

class Tên_class

{

//Định nghĩa các thuộc tính;

//Định nghĩa các phương thức;

static void Main(string[] args)

{

//Khai báo biến, hằng;

//Thân phương thức Main()

Console.ReadLine(); //Phương thức dừng màn hình để xem kết quả

}

}

}

Common Language Runtime

Trung tâm của .NET framework là môi trường thời gian chạy, gọi là Common Language Runtime (CLR) hoặc .NET runtime. Mã của các điều khiển trong CLR thường là mã có quản.

Tuy nhiêu, trước khi được thực thi bởi CLR, mã được phát triển trong C# (hoặc các ngôn ngữ khác) cần phải được biên dịch.Quá trình biên dịch trong .NET xảy ra theo hai bước:

  1. Dịch mã nguồn thành Microsoft Intermediate Language (MS-IL) (C# 3.0 gọi là Common Intermediate Language - CIL)

  2. Dịch IL thành mã nền cụ thể bởi CLR

Mới nhìn có vẻ hơi dài dòng. Nhưng thật sự, một tiến trình dịch hai mức là rất cần thiết, bởi vì trạng thái của Microsoft Intermediate Language (mã có quản) là chìa khóa cung cấp nhiều lợi ích trong .NET.

Các lợi ích của mã có quản

Microsoft Intermediate Language (thường được viết tắt là"Intermediate Language", hay "IL") tương tự như ý tưởng về mã Java byte, nó là một ngôn ngữ cấp thấp với những cú pháp đơn giản (dựa trên cơ sở mã số hơn là text), chính điều này sẽ làm cho quá trình dịch sang mã máy nhanh hơn. Hiểu kĩ các cú pháp này sẽ mang lại những lợi ích đáng kể.

Độc lập nền

Trước tiên, nó có nghĩa là các file chứa mã lệnh có thể chạy trên bất kì nền nào, vào thời gian chạy trình biên dịch cuối sẽ hoạt động và mã có thể chạy trên một nền cụ thể. Nói cách khác việc dịch mã nguồn sang Intermediate Language cho phép độc lập nền trong .NET, nó giống như cách dịch mã nguồn sang Java byte code cung cấp sự độc lập nền trong Java.

Bạn cũng nên biết rằng sự độc lập nền của .NET chỉ là trên lí thuyết bởi vì tại thời điểm này, .NET chỉ có sẵn trong Windows. Tuy nhiên việc chuyển .NET sang những nền khác đang được khảo sát tỉ mỉ (xem ví dụ Mono project, một sự cố gắng tạo một thực thi mã nguồn mở trong .NET, tại địa chỉ  http://www.go-mono.com/).

Sự cải tiến trong thực thi

Mặc dù chúng ta đã so sánh với Jave, IL thật sự có một chút khả quan hơn Java. IL luôn là trình biên dịch Just-In-Time, ngược lại Java byte code thì thường là thông dịch. Một trong những bất lợi của Java là vào lúc thực thi quá trình dịch từ java byte code sang mã máy tốn nhiều tài nguyên.

Thay vì phải dịch toàn bộ ứng dụng một lần, trình biên dịch JIT sẽ biên dịch từng phần mã khi nó được gọi. Khi mã được dịch rồi, mã kết quả sẽ được giữ lại cho tới khi thoát khỏi ứng dụng, chính vì thế nó không phải biên dịch lại trong lần chạy kế tiếp. Microsoft quả quyết rằng cách xử lí này có hiệu lực cao hơn là dịch toàn bộ ứng dụng, bởi vì có trường hợp một khối lượng lớn mã của ứng dụng không bao giờ được sử dụng trong thời gian chạy. Khi sử dụng trình biên dịch JIT , các đoạn mã này sẽ không bao giờ được dịch.

Chính vì thế chúng ta hi vọng rằng mã IL sẽ thực thi nhanh như là mã máy. Microsof  cam kết chúng ta sẽ có một thay đổi lớn trong thực thi. Lời lí giải là, là lần dịch cuối cùng trong thời gian chạy, trình biên dịch JIT sẽ biết chính xác loại vi xử lí mà chương trình sẽ chạy. Có nghĩa là nó có thể tối ưu mã thi hành cuối cùng bằng cách tham chiếu đến các đặc trưng của từng các bộ lệnh ứng với các loại vi xử lí đó.

Các trình biên dịch truyền thống đều có tối ưu mã, nhưng chúng chỉ có thể tối ưu độc lập không quan tâm đến loại vi xử lý mà chương trình sẽ chạy. Bởi vì trình biên dịch truyền thống biên dịch toàn bộ ứng dụng sang mã máy trước khi thực thi. Có nghĩa là trình biên dịch không hề biết loại vi xử lí mà chương trình sẽ được chạy, chẳng hạn nó có thể là một vi xử lí tương thích x86 hoặc một vi xử lí Alpha. Visual Studio 6, tối ưu cho cho một máy tương thích Pentium, bởi vậy mã mà nó sinh ra không đem lại  lợi ích gì đối với các đặc trưng phần cứng của vi xử lí Pentium III. Trong khi đó, trình biên dịch JIT có thể thực hiện tối ưu giống như Visual Studio 6, ngoài ra nó còn có thể tối ưu cho các loại vi xử lí cụ thể mà mã chương trình sẽ chạy.

Tương hoạt giữa các ngôn ngữ

Chúng ta đã biết cách thức IL cho phép độc lập nền, trình biên dịch JIT có thể cải thiện quá trình thực thi. Tuy nhiên, IL cũng làm cho tương hoạt giữa các ngôn ngữ trở nên dễ dàng hơn. Bạn có thể biên dịch IL từ một ngôn, và mã này sau đó có thể tương hoạt với IL được biên dịch bởi một ngôn ngữ khác.

Bây giờ chắc bạn đang tự hỏi rằng những ngôn ngữ nào có thể tương tác với C# trong .NET, hãy xem qua các ngôn ngữ biết .NET phổ biến sau.

VB.NET

Visual Basic đã được tân trang lại để có thể tương thích với công nghệ .NET. Từ việc phát triển Visual Basic trong những năm gần đây cho thấy rằng trong các phiên bản trước, Visual Basic 6, nó không tương thích với lập trình .NET. Ví dụ, nó đặt nặng vấn đề tích hợp COM, và nó chỉ đưa ra các sự kiện để phát triển, hầu hết mã nền không có sẵn trong mã nguồn. Không những thế, nó không thực sự hỗ trợ tính thừa kế và các kiểu dữ liệu chuẩn của Visual Basic không tương thích với .NET.

Visual Basic đang được hoàn thiện trong Visual Basic .NET, cũng đừng ngạc nhiên khi sự thay đổi này xảy ra trên một diện rộng. Về phương diện thực hành bạn có thể xem VB.NET như là một ngôn ngữ mới. Mã VB 6 không không thể được biên dịch trong như mã VB.NET. Sự chuyển đổi từ lập trình VB 6 sang VB.NET yêu cầu một sự thay đổi lớn về mã. Tuy nhiên hầu hết các sự thay đổi này có thể được thực hiện một cách tự động bởi Visual Studio .NET (sự cải tiến của VS cho việc sử dụng .NET). Nếu bạn cố gắng đọc một đề án VB 6 trong Visual Studio .NET, nó sẽ cải tiến đề án của bạn, có nghĩa là nó sẽ viết lại mã nguồn VB 6 thành mã nguồn VB.NET. Điều đó có nghĩa là việc này sẽ gặp rắc rối khi bạn cắt ngang, bạn sẽ phải kiểm tra lại mã VB.NET mới để chắc rằng đề án của bạn vẫn chạy đúng.

Một hiệu ứng phụ là không còn khả năng biên dịch .NET sang mã thực thi. VB.NET chỉ biên dịch sang IL, giống như C#. Nếu như bạn muốn tiếp tục mã hóa trong VB 6, bạn có thể làm như vậy, nhưng khi mã thực thi quá dài nó sẽ lờ đi .NET Framework, và bạn cần phải giữ lại Visual Studio 6 đã cài đồng thời phải hoàn toàn tin vào môi trường phát triển trong Visual Studio.

Managed C++

Vào lúc đó trong Visual C++ 6, C++ đã có một khối lượng lớn các mở rộng của Microsoft trong Windows. Với Visual C++ .NET, các mở rộng này được tăng thêm cho việc hỗ trợ .NET framework. Có nghĩa là mã nguồn C++ sẽ vẫn tiếp tục được dịch sang mã máy không có gì khác biệt. Cũng có nghĩa là nó sẽ chạy độc lập trong môi trường .NET. Nếu bạn không muốn mã C++ của bạn chạy trong môi trường .NET Framework, bạn có thể đơn giãn đặt dòng lệnh sau vào đầu mã nguồn của bạn:

#using <mscorlib.dll>

Bạn cũng có thể bỏ qua cờ /clr trong trình biên dịch, cờ này cho biết rằng bạn muốn biên dịch sang mã có quản, và nó sẽ phát ra IL thay vì mã máy. Có một điều thú vị trong C++ khi bạn biên dịch sang mã có quản, trình biên dịch có thể phát ra các IL có nhúng các mã máy. Điều này có nghĩa là bạn có thể pha trộn kiểu có quản và kiểu không quản trong mã C++. Bằng cách mã C++:

class MyClass
{

Định nghĩa cho một lớp trong C++ , trong khi đó mã:

__gc class MyClass
{

sẽ cho bạn một lớp có quản, giống như việc bạn viết một lớp trong C# hay VB.NET. Thật vậy, một thuận lợi của managed C++ so với C# là bạn có thể gọi các lớp không quản C++ từ mã có quản C++ bỏ qua tương thích COM.

Trình biên dịch sẽ phát ra một lỗi nếu bạn cố gắng dùng những đặc trưng mà mã có quản của .NET không hỗ trợ trong (ví dụ, khuôn mẫu hay đa thừa kế). Bạn cũng sẽ nhận ra rằng bạn sẽ phải dùng các đặc trưng không thuần C++ (chẳng hạn từ khoá __gc trong ví dụ trên) khi sử dụng các lớp có quản.

Bởi vì trong VC++ cho phép giải phóng bộ nhớ thủ công dưới dạng một con trỏ, trình biên dịch C++ không thể phát ra mã cho kiểu bộ nhớ an toàn CLR. Nếu ứng dụng của bạn thật sự cần phải nhận dạng kiểu bộ nhớ an toàn CLR, bạn cần phải viết mã nguồn trong các ngôn ngữ khác (như C# hay VB.NET).

J++ và J#

J++ vẫn được hỗ trợ cho chỉ vì mục đích tương thích trước đây. Microsoft không còn phát triển bất kì nền tảng nào hỗ trợ việc biện dịch sang máy Java ảo. Thay vì đó, Microsoft phát triển hai công nghệ tách biệt Java/J++ phát triển bên dưới ngọn cờ JUMP (Java User Migration Path) và "JUMP trong .NET".

Công nghệ đầu tiên là Visual J#. Về bản chất nó được thêm vào Visual Studio.NET để cho phép bạn viết và biên dịch mã J++. Sự khác biệt đó là thay vì biên dịch sang một Java Virtual Machine, J# sẽ biên dịch sang IL, vì vậy nó sẽ hoạt động như là một ngôn ngữ của .NET. Ngừơi dùng J# sẽ có thể được hưởng các thuận lợi của các đặc tính của VS.NET. Microsoft tin răng người dùng J++ sẽ nhanh chóng nhận ra điều đó nếu họ thích làm việc trong với .NET.

Sự lựa chọn thứ hai là công cụ tự động hỗ trợ việc chuyển mã J++ sang mã C#. Sự giống nhau giữa J++ và C# làm cho việc chuyển đổi này trở nên dễ dàng hơn.

Không giống như J# cũng không như các công cụ chuyển đổi ngôn ngữ có sẵn như là một phần của .NET hay trong Visual Studio. NET, thay vì thế nó được cung cấp riêng.

Scripting languages

Scripting languages đâu đó vẫn còn tồn tại, dù rằng về mặt tổng quát, tầm quan trọng của chúng đã giảm sút cùng với sự ra đời của .NET. JScript, được cải tiến lên JScript.NET. ASP.NET (một cải tiến từ ASP dành cho .NET, giải thích sau) các trang có thể được viết bằng JScript.NET, và bây giờ nó có thể chạy trên JScript.NET như là một ngôn ngữ biên dịch hơn là một ngôn ngữ thông dịch và nó có thể tạo ra các mã kiểu mã JScript.NET mạnh hơn. Với ASP.NET không có lí do gì để dùng scripting languages trên cac trang web server-side. VBA vẫn được sử dụng như là một ngôn ngữ cho Microsoft Office và Visual Studio macros.

COM và COM+

COM và COM+ không là công nghệ chính của .NET, bởi vì các thành phần cơ bản của chúng không thể dịch sang IL (mặc dù vẫn có thể làm điều đó khi tổ chức thành phần COM bằng mã C++). Tuy nhiên COM+ vẫn còn là một công cụ quan trọng, từ khi đặc tính của nó được nhân lên trong .NET. Ngoài ra, thành phần COM vẫn còn làm việc và .NET kết hợp chặc chẽ các đặc trưng tương hoạt COM để mã có quản có thể gọi đến COM và ngược lại.

Class

Lớp (Class) đại diện cho một nhóm các đối tượng tương tự nhau về đặc điểm và hành vi.

Trong lập trình hướng đối tượng điều quan trọng là phải tạo ra được các đối tượng, nhưng không phải có bao nhiêu đối tượng ta tạo ra hết, mà ta có cách hay hơn, đó là những đối tượng có những đặc điểm và hành vi tương tự nhau (ví dụ như các đối tượng Sinh viên chẳng hạn) ta gộp chung lại thành một nhóm. Như vậy bài toán lúc này sẽ là tạo ra nhóm đối tượng đại diện cho các đối tượng tương tự nhau, từ nhóm đối tượng như vậy ta có thể tìm đến từng đối tượng cụ thể một thông qua những đặc điểm và hành vi cụ thể của đối tượng đó. Trong C#, nhóm đối tượng như vậy  gọi là lớp (class), các đặc điểm của các đối tượng gọi thuộc tính (property), còn các hành vi gọi là phương thức (method).

Cú pháp tạo lớp:

class Tên_lớp

{

Khai_báo_các_thuộc_tính;

Định_nghĩa_các_phương_thức;

}

Ví dụ:

class Student

{

string studentid;

string studentname;

 

}

Cách tạo một đối tượng từ lớp:

Tên_lớp Tên_đối_tượng = new Tên_lớp();

Truy xuất thuộc tính của đối tượng:

Tên_đối_tượng.Tên_thuộc_tính;

Sử dụng phương thức của đối tượng:

Tên_đối_tượng.Tên_phương_thức();

Phương thức tĩnh:

Để gọi đến một phương thức nào đó của lớp thì thông thường ta phải tạo một đối tượng của lớp đó và sử dụng đối tượng này để gọi phương thức. Tuy nhiên, với phương thức tĩnh thì lại khác, bạn được quyền gọi trực tiếp một phương thức tĩnh mà không phải thông qua bất kỳ một đối tượng nào.

Để tạo phương thức tĩnh đơn giản bạn chỉ cần sử dụng từ khóa static khi tạo phương thức, cụ thể như sau:

[Tầm_vực] static Kiểu_trả_về Tên_phương_thức(Đối_số_nhận_dữ_liệu)

{

Thân_phương_thức;

}

Phương thức tĩnh có thể trực tiếp truy cập tới biến tĩnh cũng như những phương thức tĩnh khác của lớp.

Phương thức tĩnh đương nhiên cũng có thể truy cập các biến và phương thức không tĩnh thông qua đối tượng.

Phương thức tĩnh không thể gọi trực tiếp tới phương thức không tĩnh trong một lớp.

Thuộc tính tĩnh:

Cũng có ý nghĩa tương tự như phương thức tĩnh.

Bổ từ truy cập:

public

private

protected

internal

protected internal

Từ khóa "ref" và "out":

- ref sẽ tiến hành truyền đối số dưới dạng một tham chiếu. Nếu có sự thay đổi giá trị từ đối số nhận thì lập tức giá trị đó cũng sẽ được truyền về đối số truyền. Lưu ý là đối số được truyền phải được khởi tạo giá trị từ đầu trước khi truyền đi.

Ví dụ:

class Program
    {
        static void Nhaplieu(ref int N)
        {
            int.TryParse(Console.ReadLine(), out N);
        }
        static void Main(string[] args)
        {
            int N=0;
            Console.Write("N = ");
            Nhaplieu(ref N);
            Console.WriteLine("Sau khi nhap, N = "+N);
            Console.ReadLine();
        }
    }

- out cũng có ý nghĩa tương tự như ref, tuy nhiên đối số được truyền không cần phải được khởi tạo giá trị, mà đối số nhận dữ liệu mới phải được khởi tạo giá trị.

Ví dụ:

  class Program
    {
        static void Nhaplieu(out int N)
        {
            int.TryParse(Console.ReadLine(), out N);
        }
        static void Main(string[] args)
        {            
            int N;
            Console.Write("N = ");
            Nhaplieu(out N);
            Console.WriteLine("Sau khi nhap, N = "+N);
            Console.ReadLine();
        }
    }

Lớp tĩnh là lớp sử dụng từ khóa static, lớp tĩnh được coi là lớp trừu tượng (abstract), và vì vậy bạn không thể tạo đối tượng từ lớp này.

Nạp chồng phương thức:

Trong một lớp bạn có thể tạo nhiều phương thức có cùng tên, nhưng khác nhau về kiểu trả về, lượng đối số, kiểu dữ liệu của từng đối số; điều này gọi là nạp chồng phương thức (overloading).

Ví dụ:

class Program
    {
        static int Tinhtong(int a, int b)
        {
            return a + b;
        }
        static float Tinhtong(int a, float b)
        {
            return a + b;
        }
        static double Tinhtong(float c, double d)
        {
            return c + d;
        }
        static void Main(string[] args)
        {
            int a = 5, b = 10;
            float c = 15;
            double d = 20;
            Console.WriteLine("Tong1 = " + Tinhtong(a, b));
            Console.WriteLine("Tong2 = " + Tinhtong(a, c));
            Console.WriteLine("Tong3 = " + Tinhtong(c, d));
            Console.ReadLine();
        }
    }

Từ khóa this:

this đại diện cho đối tượng hiện thời của phương thức.

Thường được dùng để giải quyết vấn đề trùng tên giữa các biến là đối số nhận dữ liệu và biến cục bộ của phương thức.

Ví dụ:

class Program
    {
        int width, length;
        Program(int width,int length) //hàm tạo của lớp Program
        {
            this.width = width; //this.width là trường width của lớp Program
            this.length = length; //this.length là trường length của lớp Program
        }
        static void Main(string[] args)
        {
            Console.ReadLine();
        }
    }

Constructor (hàm tạo):

Hàm tạo có mục đính là để khởi tạo các giá trị ban đầu cho các trường của đối tượng khi tạo đối tượng.

Hàm tạo sẽ tự động được thực thi mỗi khi tạo một đối tượng.

Tên của hàm tạo phải luôn giống với tên lớp chứa nó, hàm tạo có thể không có, hoặc có 1, 2, 3, ... đối số tùy thuộc vào việc khởi tạo như thế nào cho các trường của lớp.

Trong trường hợp bạn không xây dựng hàm khởi tạo cho lớp thì trình biên dịch sẽ tạo constructor mặc định cho lớp đó và đó là hàm khởi tạo không đối số, trong đó hàm sẽ gán giá trị chính là các giá trị mặc định tương ứng với kiểu dữ liệu của trường của lớp, ví dụ chuỗi rỗng cho trường kiểu string, 0 cho trường kiểu số,  false cho trường kiểu bool.

Cú pháp:

class Tên_lớp

{

Kiểu_dữ_liệu Trường1;

Kiểu_dữ_liệu Trường2;

...

Tên_lớp(Các_đối_số_nhận_giá_trị_khởi_tạo)

{

Trường1 = Giá_trị1;

Trường2 = Giá_trị2;

...

}

}

Ví dụ:

class Program
{
    int width, length;
    Program() //hàm tạo 0 đối số
    {
        width = 0;
        length = 0;
    }
    Program(int width) //hàm tạo 1 đối số
    {
        this.width = width;
    }
    Program(int width,int length) //hàm tạo 2 đối số
    {
        this.width = width;
        this.length = length;
    }
    static void Main(string[] args)
    {

        Program p = new Program(); //kích hoạt hàm tạo 0 đối số
        Program p1 = new Program(10); //kích hoạt hàm tạo 1 đối số
        Program p2 = new Program(10, 20); //kích hoạt hàm tạo 2 đối sô
        Console.ReadLine();
    }
}

Lớp tĩnh:

Sử dụng từ khoá static để tạo lớp tĩnh.

Lớp tĩnh được quyền khai báo các thành phần tĩnh nhưng không truy cập được.

Lớp tĩnh không được phép có phần thể hiện.

Thừa kế được lớp khác.

Cho phép thừa kế các thành phần public của nó, không cho phép với protected.

Không truy cập được vào các thành phần không tĩnh.

Sử dụng tên lớp trực tiếp để truy xuất các thành phần tĩnh.

Lớp Object

Trong C#, nếu bạn không chỉ ra một lớp được thừa kế từ lớp khác thì trình biên dịch sẽ tự động hiểu rằng lớp của bạn thừa kế từ lớp Object. Đều này có nghĩa là ngoài những thuộc tính và phương thức mà bạn định nghĩa thì bạn có thể truy cập đến những phương thức protected và public của lớp Object. Và những phương thức này cũng có trong tất cả lớp mà bạn định nghĩa.

Một số phương thức được định nghĩa trong lớp Object là:

Phương thức

Bổ từ truy cập

Chức năng

string ToString()

public virtual

Trả về một chuỗi mô tả của đối tượng

int GetHashTable()

public virtual

Được sử dụng nếu thi hành từ điển

bool Equals(object obj)

public virtual

So sánh các thực thể của đối tượng

bool Equals(object objA, object objB)

public static

So sánh các thực thể của đối tượng

bool ReferenceEquals(object objA, object objB)

public static

So sánh hai sự tham khảo đến một đối tượng

Type GetType()

public

Trả về chi tiết kiểu của một đối tượng.

object MemberwiseClone()

protected

tạo ra một bản copy của đối tượng

void Finalize()

protected virtual

Đây là một dạng Destructor của .Net

Chúng ta không thể hiểu chính xát cách sử dụng của mọi phương thức trong lớp Object vào giai đoạn này.

Phương thức ToString()

Phương thức ToString() rất tiện lợi khi bạn muốn lấy một chuỗi mô tả đối tượng. Ví dụ:

int i = -50;

string str = i.ToString(); //returns "-50"

Thêm một ví dụ khác :

enum Colors {Red, Orange, Yellow};

// later on in code...

Colors favoriteColor = Colors.Orange;

string str = favoriteColor.ToString(); //returns "Orange"

Object.ToString() được khai báo như virtual . Trong C#, các kiểu dữ liệu có sẳn đã được override cho chúng ta để trả chuỗi đúng với sự mô tả của các kiểu đó. Nếu bạn không override phương thức ToString() trong lớp bạn định nghĩa, thì lớp bạn sẽ thừa kế sự thực thi phương thức ToString() của system.Object và xuất ra tên lớp của bạn. Nếu bạn muốn phương thức ToString() trả về một chuỗi chứa nội dung thông tin về giá trị của đối tượng của lớp bạn tạo ra thì bạn phải override phương thức ToString().

Ví dụ:

using System;
namespace Wrox.ProCSharp.OOCSharp
{
  class Money
  {
    private decimal amount;
    public decimal Amount
    {
      get
      {
        return amount;
      }
      set
      {
        amount = value;
      }
    }
  }
  class BetterMoney : Money
  {
    public override string ToString()
    {
      return "$" + Amount.ToString();
    }
  }
  class MainEntryPoint
  {
    static void Main(string[] args)
    {
      Money cash1 = new Money();
      cash1.Amount = 40M;
      Console.WriteLine("cash1.ToString() returns: " + cash1.ToString());
      cash1 = new BetterMoney();
      cash1.Amount = 40M;
      Console.WriteLine("cash1.ToString() returns: " + cash1.ToString());
      Console.ReadLine();
    }
  }
}

Trong ví dụ trên, phương thức Main() trước tiên khởi tạo đối tượng cash1 của lớp Money, sau đó lại cũng khởi tạo chính đối tượng cash1 nhưng là của lớp BetterMoney. Trong cả hai trường hợp cash1 đều gọi phương thức ToString() nhưng với đối tượng cash1 của lớp Money thì sẽ thực thi phương thức của lớp System.Object, còn với đối tượng cash1 của lớp BetterMoney thì sẽ thực thi phương thức override phương thức ToString(). Kết quả sau khi chạy đoạn mã trên sẽ là:

cash1.ToString() returns: Wrox.ProCSharp.OOCSharp.Money
cash1.ToString() returns: $40

Nạp chồng toán hạng

Điểm nổi bật của nạp chồng toán hạng là không phải lúc nào bạn cũng muốn gọi các phương thức hay thuộc tính trên các thể hiện lớp. Chúng ta thường cần làm một số công việc như cộng các số lượng với nhau, nhân chúng hay thực hiện một số toán hạn logic như so sánh các đối tượng. Ví dụ ta định nghĩa một lớp mô tả ma trận toán học. Các ma trận thì có thể cộng, nhân với nhau như các số, nên ta có thể viết đoạn mã như sau:

Matrix a, b, c;
//chẳng hạn a, b, c đã được khởi tạo
Matrix d = c * (a + b);

Bằng nạp chồng các toán hạng ta có thể làm cho trình biên dịch biết những gì mà + và * làm đối với một ma trận, và bạn có thể viết đoạn mã như trên. Nếu như không sử dụng toán hạng nạp chồng như trên, ta cũng có thể định nghĩa các phương thức để thực hiện các toán hạng trên nhưng nó sẽ có rất nhiều hỗn độn:

Matrix d = c.Multiply(a.Add(b));

Các toán hạng như + và * rất khắc khe với các kiểu dữ liệu định nghĩa trước, và do đó trình biên dịch sẽ tự động biết ý nghĩa của các toán hạng dựa trên các kiểu dữ liệu đó. Ví dụ như nó biết cách để cộng hai số kiểu long, hay cách để chia một số kiểu double cho một số kiểu double. Khi chúng ta định nghĩa lớp hay struct chúng ta phải nói với trình biên dịch mọi thứ như: những phương thức nào có thể được gọi, những trường nào được lưu trữ với mọi thực thể và vân vân. Nếu chúng ta sử dụng các toán hạng như +, * trong lớp của chúng ta. Chúng ta phải nói với trình biên dịch biết ý nghĩa của những toán hạng có liên quan trong ngữ cảnh của lớp đó. Và cách chúng ta làm là định nghĩa nạp chồng cho các toán hạng.

Một số trường hợp chúng ta nên viết các toán hạng nạp chồng:

1. Trong thế giới toán học, mọi đối tượng toán học như: tọa độ, vector, ma trận, hàm số và vân vân. Nếu bạn viết chương trình làm những mô hình toán học hay vật lý, bạn nhất định sẽ mô tả những đối tượng này.

2.Những chương trình đồ hoạ sẽ sử dụng các đối tượng toán học và toạ độ khi tính toán vị trí của trên màn hình.

3. Một lớp mô tả số lượng tiền.

4. Việc sử lý từ hay chương trình phân tích văn bản có lớp để mô tả các câu văn, mệnh đề và bạn phải sử dụng các toán hạng để liên kết các câu lại với nhau.

Cách hoạt động của các toán hạng :

 Để hiểu cách nạp chồng toán hạng, chúng ta phải nghĩ về những gì xảy ra khi trình biên dịch gặp một toán hạng - :

int a = 3;
uint b = 2;
double d = 4.0;
long l = a + b;
double x = d + a; 

Xem dòng lênh:

long l = a + b;

Việc thực hiện a+b như trên là rất trực quan, đó là một cú pháp tiện lợi để nói rằng chúng ta đang gọi phương thức cộng hai số.

Trình biên dịch sẽ thấy nó cần thiết để cộng hai số nguyên và trả về số kiểu long. Ta thấy đây là phép cộng hai số kiểu integer và kết quả cũng là một số integer nhưng nó ép kiểu sang kiểu long và điều này thì cho phép trong C#.

Xét dòng lệnh:

double x = d + a;

Ta thấy trong nạp chồng này có số kiểu double và kiểu integer, cộng chúng lại và trả về kiểu doube. Chúng ta cần phải đổi kiểu int sang kiểu double sau đó cộng hai số đó lại với nhau. Và chúng ta nhận ra sự nạp chồng của toán tử cộng ở đây như là một phiên bản của toán tử nhận hai số double như hai tham số. Và trình biên dịch phải chắc là nó có thể ép kiểu kết quả về một kiểu thích hợp nếu cần.

Xét đoạn mã sau:

Vector vect1, vect2, vect3;
// initialise vect1 and vect2
vect3 = vect1 + vect2;
vect1 = vect1*2;
 

Ở đây vector là một struct, trình biên dịch cần phải cộng hai vector  vect1 và vect2 với nhau. Và nó sẽ tìm một nạp chồng của toán hạng + lấy hai vector như tham số của nó. Và toán hạng này trả về một vector khác. Bởi vậy trình biên dịch cần tìm một định nghĩa của toán hạng có dạng như sau:

public static Vector operator + (Vector lhs, Vector rhs)

Nếu tìm ra nó sẽ thực thi toàn hạng đó. Nếu không nó sẽ sử dụng bất kỳ nạp chồng của toán hạng + nào có hai tham số kiểu dữ liệu khác và có thể chuyển sang thực thể vector. Nếu không tìm được cái nào thích hợp thì nó sẽ báo lỗi.

Ví dụ về nạp chồng toán hạng : struct Vector

Chúng ta sẽ định nghĩa một struct Vector, nó mô tả một vector ba chiều.

Một vector ba chiều là một tập hợp ba con số kiểu double. Các biến mô tả các con số được gọi là x, y, z. Liên kết ba con số lại với nhau và để chúng tạo thành một vector toán học.

Sau đây là định nghĩa cho Vector- chứa các trường thành viên, contructor, và một phương thức ToString() overriden, vì thế chúng ta có thể dễ dàng thấy nội dung của một vector và cuối cùng là nạp chồng toán hạn:

namespace Wrox.ProCSharp.OOCSharp
{
   struct Vector
   {
      public double x, y, z;

      public Vector(double x, double y, double z)
      {
         this.x = x;
         this.y = y;
         this.z = z;
      }

      public Vector(Vector rhs)
      {
         x = rhs.x;
         y = rhs.y;
         z = rhs.z;
      }

      public override string ToString()
      {
         return "( " + x + " , " + y + " , " + z + " )";
      }

Chú ý rằng để làm cho mọi thứ đơn giản thì mọi trường nên được khai báo public. Nên nhớ các struct không cho phép để contructor mặc định. Do đó, tôi đã tạo hai constructor để khởi tạo giá trị cho vector bằng cách truyền giá trị cho mọi phần tử hay truyền một vector khác để sao chép các giá trị của vector này.

Bây giờ hãy xem qua một nạp chồng toán hạng:

      public static Vector operator + (Vector lhs, Vector rhs)
      {
         Vector result = new Vector(lhs);
         result.x += rhs.x;
         result.y += rhs.y;
         result.z += rhs.z;
         return result;
      }
   }
}

Nó làm việc như thế nào? Cú pháp quan trọng là trong khai báo của toán hạng. Nó được khai báo như cách khai báo một phương thức, ngoại trừ từ khoá operation sẽ nói với trình biên dịch là nó là một nạp chồng toán hạng. Toán hạng được đại diện bởi ký kiệu thực tế cho phù hợp. Kiểu trả về là kiểu mà bạn nhận được khi sử dụng toán hạng này. Trong trường hợp của chúng ta, cộng hai vector sẽ cho chúng ta một vector khác, vì thế kiểu trả về là một vector. Bạn có thể override toán hạng + này, bằng cách định nghĩa toán hạng cùng tên nhưng khác kiểu trả về. Hai tham số ở đây để chỉ hai phần tử bạn đang muốn thực hiện toán hạng giữa chúng. Ví dụ như một toán hạng có hai tham số giống như "+" ở trên, tham số đầu là một đối tượng hay giá trị nằm ở phía bên trái dấu "+", và tham số thứ hai là đối tượng hay giá trị nằm bên phải dấu "+".

Cuối cùng, chú ý toán hạng được khai báo static, với ý nghĩa là nó được kết nối với struct hoặc class, không phải với bất kỳ đối tượng nào, và vì thế không truy cập đến một con trỏ this được.

Bây giờ chúng ta đề cập đến cú pháp cho việc khai báo toán hạng +, chúng ta có thể quan sát những gì xảy ra bên trong toán hạng

      {
         Vector result = new Vector(lhs);
         result.x += rhs.x;
         result.y += rhs.y;
         result.z += rhs.z;
         return result;
      }

Phần mã này rõ ràng giống như nếu chúng ta khai báo một phương thức, và bạn có thể tin tưởng rằng nó sẽ trả về một vector chứa tổng của lhs và rhs như định nghĩa ở trên. Chúng ta chỉ đơn giản cộng các số x, y, và z một cách riêng lẽ.

Bây giờ chúng ta cần kiểm tra struct của chúng ta bằng đoạn mã sau:

      static void Main()
      {
         Vector vect1, vect2, vect3;
         vect1 = new Vector(3.0, 3.0, 1.0);
         vect2 = new Vector(2.0, -4.0, -4.0);
         vect3 = vect1 + vect2;
         Console.WriteLine("vect1 = " + vect1.ToString());
         Console.WriteLine("vect2 = " + vect2.ToString());
         Console.WriteLine("vect3 = " + vect3.ToString());
      }

Thực thi ta được kết quả sau:

Vectors
vect1 = ( 3 , 3 , 1 )
vect2 = ( 2 , -4 , -4 )
vect3 = ( 5 , -1 , -3 )

Thêm nhiều sự nạp chồng:

Mặc dù chúng ta hiểu được cách để nạp chồng một toán hạng nhưng trong thực tế ta có rất nhiều toán hạng. Ví dụ như một vector bạn có thể thực hiện cộng hai vector, nhân hai vector, trừ hai vector hoặc so sánh giá trị của nó. Ta sẽ xét ví dụ nạp chồng phép nhân vector bằng cách nhân vô hướng hai vector với nhau.

      public static Vector operator * (double lhs, Vector rhs)
      {
         return new Vector(lhs * rhs.x, lhs * rhs.y, lhs * rhs.z);
      }

Nếu a và b được khai báo kiểu vector thì nó cho phép ta viết mã như sau:

b=2*a;

nhưng nó không cho phép viết như sau:

b=a*2;

Bởi vì lúc này trình biên dich xem như toán hạng nạp chồng giống như phương thức nạp chồng. Nó kiểm tra tất cả các nạp chồng khả thể của toán hạng để tìm được sự ăn khớp tốt nhất. Như ở trên thì nó yêu cầu có tham số đầu tiên là một vector, tham số thứ hai là một số nguyên. Và tìm không thấy nó sẽ báo lỗi. Có 2 cách để xử lý trường hợp trên bằng cách nạp chồng các toán hạng * khác:

      public static Vector operator * (Vector lhs, double rhs)
      {
         return new Vector(rhs * lhs.x, rhs * lhs.y, rhs *l hs.z);
      }

Hoặc:

      public static Vector operator * (Vector lhs, double rhs)
      {
         return rhs * lhs;
      }
 

Nạp chồng các toán hạng so sánh:

Có sáu toán hạng trong C#, và chúng ta xét từng cặp:

  • = = and !=

  • > and <

  • >= and <=

Ý nghĩa của từng cặp này là gấp đôi. Giữa các cặp này luôn luôn có kết quả đối nghịch nhau: Nếu toán hạng đầu trả về giá trị true thì toán hạng kia trả về giá trị false. C# luôn luôn yêu cầu bạn nạp chồng cả hai toán tử đó. Nếu bạn nạp chồng toán tử "= =" thì phải nạp chồng toán tư"!=" nếu không trình biên dịch sẽ báo lỗi.

Có một hạn chế là toán hạng so sánh phải trả về kiểu bool. và đó cũng là điểm khác nhau giữa các toán hạng này và toán hạng số học.

Xét ví dụ ta override toán hạng = = và != cho lớp vector:

      public static bool operator = = (Vector lhs, Vector rhs) 
      {
         if (lhs.x = = rhs.x && lhs.y = = rhs.y && lhs.z = = rhs.z)
            return true;
         else
            return false;
      }

Các vector so sánh được xét bằng nhau trên các giá trị của các thành phần. Đều cần chú ý là bạn đang xét sự bằng nhau giữa hai vector theo sự tham khảo hay theo giá trị của nó.

Và chúng ta cũng làm tương tự cho toán hạng !=:

      public static bool operator != (Vector lhs, Vector rhs)
      {
          return ! (lhs = = rhs);
      }

Ta có đoạn chương trình sau:

  static void Main()
      {
         Vector vect1, vect2, vect3;
         vect1 = new Vector(3.0, 3.0, -10.0);
         vect2 = new Vector(3.0, 3.0, -10.0);
         vect3 = new Vector(2.0, 3.0, 6.0);
         Console.WriteLine("vect1= =vect2 returns  " + (vect1= =vect2));
         Console.WriteLine("vect1= =vect3 returns  " + (vect1= =vect3));
         Console.WriteLine("vect2= =vect3 returns  " + (vect2= =vect3));
         Console.WriteLine();
         Console.WriteLine("vect1!=vect2 returns  " + (vect1!=vect2));
         Console.WriteLine("vect1!=vect3 returns  " + (vect1!=vect3));
         Console.WriteLine("vect2!=vect3 returns  " + (vect2!=vect3));
      }

Khi chạy và biên dịch, trình biên dịch sẽ cảnh báo bạn không override phương thức Equals() cho vector. Nhưng với mục đích của chúng ta thì nó không có ý nghĩa gì:

Vectors3
vect1= =vect2 returns  True
vect1= =vect3 returns  False
vect2= =vect3 returns  False

vect1!=vect2 returns  False
vect1!=vect3 returns  True
vect2!=vect3 returns  True

Những toán tử nào bạn có thể nạp chồng:

Phạm trù

Toán hạng

Hạn chế

Nhị phân toán học

+, *, /, -, %

Không

Thập phân toán học

+, -, ++, --

Không

Nhị phân bit

&, |, ^, <<, >>

Không

Thập phân bit

!, ~, true, false

Không

So sánh

==, !=, >=, <, <=, >

Phải nạp chồng theo từng cặp.

Interface

C# hỗ trợ giao diện (Interface). Khi thừa kế một giao diện, một lớp đang khai báo sẽ thực thi những hàm nào đó. Chúng ta sẽ minh họa về giao diện thông qua việc giới thiệu một giao diện đã được Microsoft định nghĩa, System.IDisposable. IDisposable chứa một phương thức Dispose() dùng để xoá mã.

public interface IDisposable
{
   void Dispose();
}

Trên ví dụ trên ta thấy việc khai báo một giao diện làm việc giống như việc khai báo một lớp Abstract, nhưng nó không cho phép thực thi bất kỳ một thành phần nào của giao diện. Một giao diện chỉ có thể chứa những khai báo của phương thức, thuộc tính, bộ phận lập mục lục, và sự kiện.

Bạn không thể khởi tạo một giao diện thực sự mà nó chỉ chứa những thành phần bên trong nó. Một giao diện thì không có Constructor hay các trường. Một giao diện thì không cho phép chứa các phương thức nạp chồng.

Nó cũng không cho phép khai báo những bổ từ trên các thành phần trong khi định nghĩa một giao diện. Các thành phần bên trong một giao diện luôn luôn là public và không thể khai báo virtual hay static.

Định nghĩa và thi hành một giao diện

Chúng ta minh họa cách để định nghĩa và sử dụng giao diện bằng việc phát triển những ví dụ ngắn trình bày mẫu thừa kế giao diện. Ví dụ về những account ngân hàng. Chúng ta sẽ viết mã để cho phép các máy điện toán chuyển khoảng qua lại giữa các account. Có nhiều công ty thực thi account và đều đồng ý với nhau là phải thi hành một giao diện IBankaccount có các phương thức nạp hay rút tiền và thuộc tính trả về số tài khoản.

Để bắt đầu, ta định nghĩa giao diện IBank:

namespace Wrox.ProCSharp.OOCSharp.BankProtocols
{
   public interface IBankAccount
   {
      void PayIn(decimal amount);
      bool Withdraw(decimal amount);

      decimal Balance
      {
         get;
      }
   }
}

Chú ý: Tên của giao diện thường phải có ký tự I đứng đầu để nhận biết đó là một giao diện.

Có ý kiến là chúng ta có thể viết các lớp mô tả Account. Những lớp này có thể khác nhau hoàn toàn. Nhưng trên thực tế chúng đều thực thi giao diện IBankAccount.

Hãy bắt đầu với lớp đầu tiên:

namespace Wrox.ProCSharp.OOCSharp.VenusBank
{
   public class SaverAccount : IBankAccount
   {
      private decimal balance;

      public void PayIn(decimal amount)
      {
         balance += amount;
      }

      public bool Withdraw(decimal amount)
      {
         if (balance >= amount)
         {
            balance -= amount;
            return true;
         }
         Console.WriteLine("Withdrawal attempt failed.");
         return false;
      }

      public decimal Balance
      {
         get
         {
            return balance;
         }
      }
      public override string ToString()
      {
         return String.Format("Venus Bank Saver: Balance = {0,6:C}", balance);
      }
   }
}

Trong ví dụ trên chúng ta duy trì một trường private balance và điều chỉnh số lượng này khi tiền được nạp hay rút. Chú ý chúng ta xuất ra một thông báo lỗi khi thao tác rút tiền không thành công vì thiếu số tiền trong tài khoản.

Xét dòng lệnh sau:

public class SaverAccount : IBankAccount

Chúng ta khai báo lớp SaverAccount thừa kế giao diện IBankAccount. Tuy trên ví dụ trên ta không nêu rõ ràng lớp SaverAccount thừa kế từ một lớp nào khác nhưng trên thực tế ta có thể khai báo một lớp thừa kế từ một lớp khác và nhiều giao diện như sau:

public class MyDerivedClass : MyBaseClass, IInterface1, IInterface2

Thừa kế từ IBankAccount có nghĩa là SaverAccount lấy tất cả các thành phần của IBankAccount nhưng nó không thể sử dụng các phương thức đó nếu nó không định nghĩa lại các hành động của từng phương thức. Nếu bỏ quên một phương thức nào thì trình biên dịch sẽ báo lỗi.

Để minh họa cho các lớp khác nhau có thể thực thi cùng một giao diện ta xét ví dụ về một lớp khác là GoldAccount.

namespace Wrox.ProCSharp.OOCSharp.JupiterBank
{
   public class GoldAccount : IBankAccount
   {
      // etc
   }
}

Chúng ta không mô tả chi tiết lơp GoldAccount bởi vì về cơ bản nó giống hệt lớp SaverAccount. Điểm nhấn mạnh ở đây là GoldAccount không liên kết với VenusAccount và chúng cùng thực thi một Interface

Bây giờ ta có những lớp và chúng ta kiểm tra chúng:

using Wrox.ProCSharp.OOCSharp.BankProtocols;
using Wrox.ProCSharp.OOCSharp.VenusBank;
using Wrox.ProCSharp.OOCSharp.JupiterBank;

Chúng ta có phương thức Main():

namespace Wrox.ProCSharp.OOCSharp
{
   class MainEntryPoint
   {
      static void Main()
      {
         IBankAccount venusAccount = new SaverAccount();
         IBankAccount jupiterAccount = new GoldAccount();
         venusAccount.PayIn(200);
         venusAccount.Withdraw(100);
         Console.WriteLine(venusAccount.ToString());
         jupiterAccount.PayIn(500);
         jupiterAccount.Withdraw(600);
         jupiterAccount.Withdraw(100);
         Console.WriteLine(jupiterAccount.ToString());
      }
   }
}

Kết quả xuất ra là:

Venus Bank Saver: Balance = £100.00
Withdrawal attempt failed.
Jupiter Bank Saver: Balance = £400.00

Chúng ta có thể trỏ đến bất kỳ thể hiện của bất kỳ lớp nào thực thi cùng một giao diện. Nhưng chúng ta chỉ được gọi những phương thức là thành phần của giao diện thông qua sự tham khảo đến giao diện này. Nếu muốn gọi những phương thức mà không là thành phần trong giao diện thì ta phải tham khảo đến những kiểu thích hợp. Như ví dụ trên ta có thể thực thi phương thức ToString() mặc dù nó không là thành phần được khai báo trong giao diện IBankAccount bởi vì nó là thành phần của System.Object.

Một giao diện có thể tham khảo đến bất kỳ lớp nào thực thi giao diện đó.

Ví dụ ta có một mảng kiểu một giao diện nào đó thì các phần tử của mảng có thể tham khảo đến bất kỳ lớp nào thực thi giao diện đó:

IBankAccount[] accounts = new IBankAccount[2];
accounts[0] = new SaverAccount();
accounts[1] = new GoldAccount();

Thừa kế giao diện

C# cho phép những giao diện có thể thừa kế các giao diện khác. Khi một giao diện thừa kế một giao diện khác thì nó có thể thi hành tất cả các phương thức định nghĩa trong giao diện đó và những phương thức của nó định nghĩa. Ví dụ tao tạo ra một giao diện mới thừa kế giao diện IBanKAccount :

namespace Wrox.ProCSharp.OOCSharp.BankProtocols
{
   public interface ITransferBankAccount : IBankAccount
   {
      bool TransferTo(IBankAccount destination, decimal amount);
   }
}

Như vậy giao diện ITransferBankAccount phải thi hành tất cả các phương thức trong giao diện IBankAccount và phương thức TransferTo.

Chúng ta sẽ minh hoạ ITransferBankAccount bằng một ví dụ bên dưới về một current account. Lớp CurrentAccount được định nghĩa gần giống hệt với các lớp SaverAccountGoldAccount, vì thế trong đoạn mã bên dưới chúng tôi đã tô màu các dòng khác nhau:

public class CurrentAccount : ITransferBankAccount
{
   private decimal balance;

   public void PayIn(decimal amount)
   {
      balance += amount;
   }

   public bool Withdraw(decimal amount)
   {
      if (balance >= amount)
      {
         balance -= amount;
         return true;
      }
      Console.WriteLine("Withdrawal attempt failed.");
      return false;
   }

   public decimal Balance
   {
      get
      {
         return balance;
      }
   }

   public bool TransferTo(IBankAccount destination, decimal amount)
   {
      bool result;
      if ((result = Withdraw(amount)) == true)
         destination.PayIn(amount);
      return result;
   }

   public override string ToString()
   {
      return String.Format("Jupiter Bank Current Account: Balance = {0,6:C}",
                                                                      balance);
   }  
}

Còn đây là đoạn code thực thi:

static void Main()
{
   IBankAccount venusAccount = new SaverAccount();
   ITransferBankAccount jupiterAccount = new CurrentAccount();
   venusAccount.PayIn(200);
   jupiterAccount.PayIn(500);
   jupiterAccount.TransferTo(venusAccount, 100);
   Console.WriteLine(venusAccount.ToString());
   Console.WriteLine(jupiterAccount.ToString());
}

Khi thực thi đoạn mã trên bạn sẽ thấy kết quả như sau:

CurrentAccount
Venus Bank Saver: Balance = £300.00
Jupiter Bank Current Account: Balance = £400.00

 

Practical 1

Exercise 1: Working with System.Console.WriteLine and System.Console.ReadLine method:

Design a program that ask user to enter name and age. After that display the welcome message to the screen.

Exercise 2: Working with System.Console.WriteLine and System.Console.ReadLine method and goto keyword

Design a program that ask user to enter name and age. If the age less than 20, ask user reenter the age.  After that display the welcome message to the screen.

Exercise 3: Working with loop construct: for, while, do.. while

Design a program that accept an integer number n and display result: n! with solutions:

· Use for statement

· Use while statement

· Use do..while statement

· Extra solution: use recursion

Exercise 4: Working with loop construct

Use loop construct or goto keyword to design a menu:

Exercise 5:

· Ask user to enter the number of football teams: n =

· Ask user to enter the name of each team

 Team 1: x

 Team 2: y

 Team 3: z

 …

· Create a random fixture for the League of n football teams (2 x (n-1) rounds).

 

Practical 2

EXERCISE 1: PRODUCT MANAGEMENT SYSTEM

1. Create class Product with some properties and methods:

· string Name

· string Description

· double Price //0 < Price <= 100

· int[ ] rate //store points which users vote the product, 1 - 5

· void ViewInfo() // display name, price and description about the product

2. Create class Shop with some properties and methods:

· ArrayList ProductList //store list of products in the shop

· void  AddProduct()  //ask user entering information about new product and add the product to ProductList

· void RemoveProduct() //ask user entering a product name and delete the product

· void IterateProductList() //iterate the Product List, call ViewInfo() of a Product, calculate average point of Product and display the information to screen.

· void SearchProduct() //ask user entering 2 numbers, after that search and display information of products that their prices between them.

3. Create a menu:

PRODUCT MANAGEMENT SYSTEM

  1. Add new product
  2. Remove product
  3. Iterate product list
  4. Search product
  5. Exit

And implement the methods in class Shop to menu.

Extra:

Add a method called SortProduct to class Shop that can sort products in product list by their prices.


EXERCISE 2: ZOO MANAGEMENT SYSTEM

1. Create class Animal has some properties and methods:

· String Name

· Int Age

· ​String Description

· ​Void ViewInfo() //display Name, Age, and Description of the animal

· Void Speak()

2. Create some constructors for class Animal that we can input n parameter(s) when creating new instance from class:

· 0 parameter

· 1 parameter ( Name),

· 2 parameters ( Name, Age),

· 3 parameters ( Name, Age, Description).

3. Create some classes: Tiger, Dog, Cat following these instructions:

  • Inherit properties and methods from class Animal
  • Override method Speak to demonstrate different sounds of different animals
  • Implement multiple constructors for these classes using constructors inheritance

4. Create class Cage has some properties and methods:

· Int CageNumber

· ​ArrayList AnimalList

· ​Void AddAnimal(Animal a) //add an animal to AnimalList

· ​Void RemoveAnimal(string name) //remove an animal has Name “name” from AnimalList

5. Create class Zoo has some properties and methods:

· ArrayList CageList

· Void AddCage(Cage c) //add a cage to CageList

· Void RemoveCage(int c) //remove a cage has CageNumber c from CageList

6. Create menu for Zoo Management System:

  1. Add Cage
  2. Remove Cage
  3. Add Animal
  4. Remove Animal
  5. Iterate Animals
  6. Exit

7. When user choose Iterate Animal, go to all the cages in Zoo, display information of all animals in cage and display their voice.

 

Abstract class & Interface

Lớp trừu tượng (Abstract class)

Giới thiệu

C# cho phép thiết kế một loại lớp đặc biệt được sử dụng khá tương tự như lớp thông thường, đó là Lớp trừu tượng (Abstract class). Loại lớp này có thể được tham chiếu như là lớp cơ sở không hoàn chỉnh, nó không được có phần thể hiện (không tạo được đối tượng từ lớp trừu tượng), nó vẫn được quyền thừa kế từ lớp khác và cho phép các lớp khác thừa kế nó.

Vậy mục đích của việc tạo ra lớp trừu tượng để làm gì? Tôi có một lớp Động vật (Animal), đương nhiên đã là động vật thì phải ăn mới tồn tại, tức là sẽ xuất hiện hành vi ăn, nhưng những loài vật khác nhau thì hành vi ăn cũng như loại thức ăn là khác nhau, chẳng hạn như lớp gia súc thì ăn cỏ, lớp động vật họ mèo thì ăn thịt. Vì thế nếu lớp Động vật được xây dựng là lớp thông thường thì khi ta định nghĩa phương thức ăn chắc chắn rằng sẽ không thể miêu tả được hành vi ăn cũng như loại thức ăn đặc trưng của tường loài. Trong trường hợp này ta xây dựng lớp Động vật là loại lớp trừu tượng và phương thức ăn ta khai báo là phương thức trừu tượng, nghĩa là không định nghĩa cụ thể, và điều này sẽ được nhường cho các lớp động vật khác nhau thừa kế từ Động vật thực thi cụ thể. Như vậy, lớp trừu tượng sẽ khai báo các phương thức chung cho tất cả các lớp dẫn xuất (thừa kế từ nó), những lớp này phải có nhiệm vụ thực thi phương thức đó nhưng theo cách thức riêng của từng lớp.

Khai báo

Lớp trừu tượng được khai báo bằng cách sử dụng từ khóa abstract và có thể có hoặc không những thành phần sau: các thành phần dữ liệu thông thường, các phương thức thông thường và các phương thức trừu tượng.

abstract class Tên_lớp {
  //Khai báo các trường
  //Định nghĩa các phương thức thông thường
  //Khai báo các phương thức trừu tượng.
}

Khi tạo một lớp trừu tượng bạn cần lưu ý các vấn đề sau:

- Thứ nhất: Không được sử dụng từ khoá sealed (nhằm mục đích không cho thừa kế). Ví dụ, cách tạo lớp trừu tượng như sau là sai:

abstract sealed class Animal { //sai vì sử dụng từ khoá sealed
}

- Thứ hai: Bổ từ truy cập của phương thức trừu tượng không được là private. Ví dụ sau đây là sai khi tạo phương thức trừu tượng:

private abstract void Move(); //sai vì bổ từ truy cập là private

- Thứ ba: Phương thức trừu tượng chỉ có thể được khai báo trong lớp trừu tượng hoặc interface. Ví dụ sau đây là sai khi đặt phương thức trừu tượng trong lớp thông thường:

class A {
  public abstract void demo(); //sai vì đặt phương thức trừu tượng trong lớp không trừu tượng
}

- Thứ tư: Bổ từ truy cập của phương thức trừu tượng là gì thì ở phần thực thi phương thức trừu tượng trong lớp dẫn xuất cũng là như vậy. Ví dụ sau đây là sai khi có sự khác nhau về bổ từ truy cập đối với phương thức trừu tượng:

abstract class A {
  protected abstract void demo();
}
class B : A {
  public override void demo() { //sai bổ từ truy cập
}
}

- Thứ năm: Không được sử dụng từ khoá virtual khi khai báo phương thức trừu tượng vì ngầm định phương thức trừu tượng đã là virtual. Ví dụ sau đây là sai vì sử dụng từ khoá virtual khi khai báo phương thức trừu tượng:

abstract class A {
  public abstract virtual void demo(); //sai, bạn không được dùng virtual khi khai báo phương thức trừu tượng
}

- Thứ sáu: Các thành phần trừu tượng trong lớp trừu tượng không được khai báo là static. Ví dụ sau đây là sai:

abstract class A {
  public abstract static void demo(); //sai, bạn không được dùng static khi khai báo phương thức trừu tượng
}

Áp dụng

Nhắc lại rằng khi trong lớp trừu tượng đã có phương thức trừu tượng thì bắt buộc những lớp thừa kế từ nó phải thực thi phương thức này. Ví dụ:

abstract class A {
  protected abtract void demo(); //Lớp A có phương thức trừu tượng
}
class B:A { //Lớp B thừa kế lớp A
  protected override void demo() { //thì bắt buộc B phải thực thi phương thức trừu tượng của A
  }
}

Đoạn mã dưới đây minh họa việc khai báo và thực thi một lớp trừu tượng.

using System;
abstract class DongVat {
  public void An() {
    Console.WriteLine("Dong vat phai an thuc an de ton tai");
  }
  public abstract void tiengKeu();
}
class SuTu : DongVat {
  public override void TiengKeu() {
    Console.WriteLine("Su tu gam");
  }
  static void Main(string[] args) {
    SuTu objSuTu = new SuTu();
    objSuTu.tiengKeu();
    objSuTu.An();
  }
}

Giải thích đoạn mã: Lớp trừu tượng DongVat được khai báo và lớp SuTu thừa kế từ nó. Vì trong lớp DongVat khai báo phương thức trừu tượng là tiengKeu(), nên lớp Sutu sẽ ghi đè phương thức này thông qua từ khóa override và thực thi nó. Phương thức Main() của lớp SuTu tiến hành gọi phương thức TiengKeu()An() sử dụng toán tử dấu chấm (.).

Giao diện (Interface)

C# không cho phép một lớp thừa kế nhiều lớp, nghĩa là nó không hỗ trợ đa thừa kế. Để khắc phục điều này thì C# đưa ra phương pháp gọi là Giao diện (Interface).

Theo đó, một lớp có thể thừa kế từ một lớp khác, đồng thời thừa kế từ nhiều interface khác nhau. Ở đây có điểm cần lưu ý là lớp được thừa kế phải đặt trước các interface. Ví dụ:

interface IA {
}
interface IB {
}
class A {
}
class B : A, IA, IB { //nếu đặt lớp A sau một interface nào đó thì trình dịch sẽ báo lỗi
}

Interface không được chứa các trường, mà chỉ có thể chứa các khai báo Property. Property phải có ít nhất một bộ truy cập (accessor) và bạn chỉ được khai báo mà không được định nghĩa accessor.

Interface cũng chỉ được phép khai báo các phương thức mà không được định nghĩa chúng.

Tất cả các thành phần của interface đều phải là public và đây là mặc định.

Sau đây là cú pháp tạo interface:

interface Tên_interface {
  Kiểu_dữ_liệu Tên_property //khai báo Property
  {
    get;
    set;
  }
  ...
  Kiểu_trả_về Tên_phương_thức(); //khai báo phương thức
  ...
}

Ví dụ:

interface IA {
  int Number {
    get;
    set;
  }
  void demo();
}

Lớp thừa kế interface bắt buộc phải thực thi tất cả các thành phần của interface, bao gồm cả Property và các phương thức của nó. Ví dụ:

interface IA {
  int Number {
    get;
    set;
  }
  void demo();
  }
class A:IA {
  int number;
  public int Number {
    get {
      return number;
    }
    set {
      number = value;
    }
  }
  public void demo() {
  }
}

Lưu ý: Interface không được có phần thể hiện (không có đối tượng), được quyền thừa kế từ các interface khác nhưng không thừa kế được từ lớp.

So sánh abstract class và interface

Giống nhau:

+ Không có thể hiện.

+ Chứa các phương thức trừu tượng.

+ Các phương thức trừu tượng được thực thi ở lớp con.

+ Thừa kế từ nhiều interface.

Khác nhau:

+ Lớp trừu tượng thừa kế từ một lớp và nhiều interface, interface chỉ thừa kế được từ nhiều interface mà không thừa kế được lớp.

+ Lớp trừu tượng định nghĩa được phương thức, interface không định nghĩa được phương thức.

+ Phương thức trừu tượng của lớp trừu tượng được thực thi bằng cách dùng từ khóa override, còn interface thì không cần nhưng bổ từ truy cập phải là public.

+ Lớp trừu tượng là lựa chọn khi bạn muốn vừa khai báo vừa định nghĩa phương thức, interface là lựa chọn phù hợp hơn khi bạn chỉ muốn khai báo các phương thức.

+ Lớp trừu tượng có hàm tạo và hàm hủy, interface không có hàm tạo và hàm hủy.

Property

C# sử dụng một tính năng gọi là Property để cho phép bạn có thể thiết lập và lấy các giá trị của các trường của lớp mà không quan tâm tới bổ từ truy cập của những trường đó là gì.

Property cho phép bạn kiểm tra giá trị trước khi gán nó cho trường của lớp.

Property cho phép bạn bảo vệ một trường của lớp bằng cách đọc hoặc ghi dữ liệu vào trường thông qua property đó.

Property có thể kiểm tra dữ liệu trước khi cho phép bạn thay đổi chúng và thực hiện những công việc cần thiết thông qua những thay đổi đó.

Property hỗ trợ đặc tính trừu tượng và đóng gói.

Cú pháp tạo property:

Bổ_từ_truy_cập Kiểu_trả_về Tên_property
{
    //Thân của property
}

Ví dụ:

public string Name
{
    set
    {
        ......
    }
    get
    {
        ......
    }
}

Bộ truy cập get và set

Các bộ truy cập này sẽ cho phép bạn đọc và gán giá trị cho một trường của lớp bằng cách thực hiện hai phương thức đặc biệt trong Property.

Bộ truy cập get dùng để đọc giá trị và nó được kích hoạt khi property được tham chiếu tới.

Bộ truy cập set dùng để ghi dữ liệu cho một trường của lớp thông qua phép toán gán và từ khóa value.

Phân loại Property

Property chỉ đọc

Trong property chỉ có bộ truy cập get.

Ví dụ:

string name;
public string Name
{
    get
    {
        return name;
    }
}

Property chỉ ghi

Trong property chỉ có bộ truy cập set.

Ví dụ:

string name;
public string Name
{
    set
    {
        name = value;
    }
}

Property đọc được và ghi được dữ liệu

Trong property có cả bộ truy cập get và set.

Ví dụ:

string name;
public string Name
{
    get
    {
        return name;
    }
    set
    {
        name = value;
    }
}

So sánh Property và Field

Property

Field

Property là thành phần dữ liệu có thể gán và truy xuất giá trị.

Field là thành phần dữ liệu dùng để lưu trữ giá trị.

Property không phải là biến và như vậy nó không sử dụng được các từ khóa ref và out

Field có thể coi là biến của lớp nên nó có thể sử dụng được các từ khóa ref và out.

Property được định nghĩa như là một tập lệnh có thể thực thi.

Field được định nghĩa bằng một câu lệnh đơn.

Property được định nghĩa với các bộ truy cập get và set.

Field không được định nghĩa với các bộ truy cập.

Property có thể thực hiện các hành động tùy chỉnh trong quá trình thay đổi giá trị của field.

Field không có khả năng thực hiện các hành động tùy chỉnh.

So sánh giữa Property và Method

Property Method
Property đại diện cho các đặc điểm của một đối tượng. Method đại diện cho các hành vi của một đối tượng.
Property chứa hai phương thức mà sẽ tự động được gọi mà không cần có tên. Method được gọi bằng cách xác định tên các phương thức và phải thông qua đối tượng của lớp.
Property không thể có tham số. Method có thể không có hoặc có nhiều tham số.
Property có thể được ghi đè nhưng không được tải chồng. Method có thể được ghi đè và cũng có thể được tải chồng.
Ví dụ: tạo một Property tên Amount public int Amount {   get(){};  set(){}; } Ví dụ: Tạo một Method tên Amount có hai đối số public int Amount (int numOne,int numTwo) { }

Khác với VB trong đó các thủ tục getset được định nghĩa như là những hàm riêng biệt, trong C# chúng được khai báo cùng nhau trong một khai báo thuộc tính. Trong VB bạn khai báo tường minh tham số cho thủ tục set và có thể chọn tên của nó, nhưng ngược lại trong C# tham số này hoàn toàn giả lập và luôn mang tên là value.

Thuộc tính Virtual và Abstract

C# cho phép bạn tạo một thuộc tính virtual hay abstract. Để khai báo một thuộc tính virtual, overriden hay abstract bạn chỉ cần thêm từ khoá đó trong lúc định nghĩa thuộc tính. Ví dụ để tạo một thuộc tính abstract thì cú pháp như sau:

public abstract string ForeName
{
   get;
   set;
}

Indexer

Trong C#, indexer cho phép các thể hiện của lớp hay struct được đánh chỉ mục giống như mảng. Indexer có cú pháp khá tương tự với property, nhưng nó không phải là property, bộ truy cập của indexer cho phép có một hoặc nhiều tham số.

Mục đích của indexer

Giả sử bạn là một giảng viên và bạn muốn thông qua hồ sơ của từng sinh viên để nắm được sự tiến bộ của các sinh viên của bạn. Có hai cách, cách thứ nhất là bạn sử dụng một số thao tác thích hợp để thiết lập (set) và lấy (get) một bản ghi cụ thể bạn muốn, điều này có thể làm bạn mất thời gian và đôi chút buồn tẻ; cách thứ hai là bạn tạo ra một indexer cho các mã sinh viên, điều này sẽ làm cho việc tiếp cận các hồ sơ dễ dàng hơn nhiều. Có được điều này là do indexer sử dụng vị trí chỉ mục của mã sinh viên để xác định vị trí của hồ sơ tương ứng.

Định nghĩa indexer

Indexer là thành phần dữ liệu cho phép bạn truy cập dữ liệu bên trong các đối tượng theo cách tương tự như truy cập mảng. Indexer cung cấp khả năng truy cập nhanh hơn tới dữ liệu trong đối tượng bởi vì nó thực hiện việc đánh chỉ mục cho những dữ liệu đó. Cách để indexer có thể truy cập được vào bên trong từng đối tượng đó chính là thông qua chỉ số của từng đối tượng đó.

Việc thực thi indexer cũng tương tự như thực thi property, chỉ khác ở một điểm đó là việc khai báo một indexer có thể có chứa tham số. Trong C#, indexer còn được biết đến như là mảng thông minh (smart array). 

Khai báo indexer

Bạn có thể áp dụng indexer cho lớp, struct hoặc interface, và để khai báo một indexer bạn cần:

· Bổ từ truy cập, nó sẽ quyết định tầm vực của indexer.

· Kiểu trả về của indexer.

· Từ khoá this, dùng để tham chiếu tới đối tượng (thể hiện) hiện thời của lớp hiện thời.

· Cặp ngoặc vuông ([]), trong đó có kiểu dữ liệu và bộ định danh cho chỉ số.

· Cặp ngoặc xoắn ({}), nơi chứa các bộ truy cập set và get.

Cú pháp:

Bổ_từ_truy_cập Kiểu_trả_về this[Các_tham_số]
{
    get
    {
        // return Giá_trị;
    }
    set
    {
        // Gán Giá_trị;
    }
}

Ví dụ:

using System;
namespace Employee
{
  class EmployeeDetails
  {
    public string[] empName = new string[2];
    public string this[int index]
    {
      get
      {
        return empName[index];
      }
      set
      {
        empName[index] = value;
      }
    }
    static void Main(string[] args)
    {
      EmployeeDetails objEmp = new EmployeeDetails();
      objEmp[0] = "Nguyen Thi Phuong Thuy";
      objEmp[1] = "Dao Thi Soi";
      Console.WriteLine("Ten nhan vien:");
      for (int i = 0; i < 2; i++)
      {
        Console.Write(objEmp[i] + "\n");
      }
    }
  }
}

Tham số của indexer

Indexer phải có ít nhất một tham số. Tham số dùng để biểu thị chỉ số của đối tượng, tham số sẽ lưu giá trị là vị trí chỉ số nơi đối tượng được gán hoặc được truy cập; điều này tương tự với việc gán hoặc truy cập giá trị trong mảng một chiều. Ngoài ra, nếu indexer có nhiều tham số thì việc truy cập cũng sẽ tương tự như đối với mảng nhiều chiều.

Về vấn đề truy cập indexer, indexer được truy cập thông qua thể hiện của lớp hiện thời kèm với chỉ số đằng sau thể hiện đó.

Thừa kế indexer

Indexer có thể được thừa kế giống như những thành phần khác của lớp, tức là những indexer của lớp cơ sở có thể được thừa kế bởi các lớp dẫn xuất. Ví dụ:

using System;
class Numbers
{
  private int[] num = new int[3];
  public int this[int index]
  {
    get
    {
      return num[index];
    }
    set
    {
      num[index] = value;
    }
  }
}
class EvenNumbers : Numbers
{
  public static void Main()
  {
    EvenNumbers objEven = new EvenNumbers();
    objEven[0] = 0;
    objEven[1] = 2;
    objEven[2] = 4;
    for (int i = 0; i < 3; i++)
    {
      Console.WriteLine(objEven[i]);
    }
  }
}

Trong ví dụ trên, lớp Numbers tạo một indexer với một đối số kiểu int, lớp EvenNumbers thừa kế lớp Numbers, trong hàm Main() tạo một thể hiện của lớp dẫn xuất EvenNumbers, khi thể hiện này được gán các giá trị tại mỗi vị trí chỉ số thì bộ truy cập set của indexer trong lớp cơ sở Numbers được gọi với mỗi vị trí chỉ số. Vòng lặp for lặp 3 lần và hiển thị các giá trị được gán cho mỗi vị trí chỉ số sử dụng bộ truy cập get. Như vậy thông qua biện pháp thừa kế thì indexer trong lớp cơ sở đã được tái sự dụng trong lớp thừa kế.

Đa hình trong indexer

Indexer còn có thể thực hiện tính đa hình bằng cách ghi đè indexer của lớp cơ sở hoặc bằng cách nạp chồng indexer.

Bằng cách thực thi tính đa hình, lập trình viên cho phép các indexer của lớp dẫn xuất ghi đè indexer của lớp cơ sở. Ngoài ra, trong cùng một lớp ta có thể tạo nhiều indexer, miễn sao những indexer này phải có những dấu hiệu khác nhau; điều này có nghĩa là ta có thể nạp chồng indexer.

Như vậy, tính đa hình cho phép indexer có thể hoạt động với các kiểu dữ liệu khác nhau của C# và tạo đầu ra (output) một cách tuỳ chỉnh.

Ví dụ, đoạn mã dưới đây sẽ cho thấy tính đa hình của indexer với việc ghi đè indexer của lớp cơ sở:

using System;
class Student
{
  string[] studName = new string[2];
  public virtual string this[int index]
  {
    get
    {
      return studName[index];
    }
    set
    {
      studName[index] = value;
    }
  }
}
class Result : Student
{
  string[] result = new string[2];
  public override string this[int index]
  {
    get
    {
      return base[index];
    }
    set
    {
      base[index] = value;
    }
  }
  static void Main(string[] args)
  {
    Result objResult = new Result();
    objResult[0] = "First";
    objResult[1] = "Pass";
    Student objStudent = new Student();
    objStudent[0] = "Luong";
    objStudent[1] = "Hoang";
    for (int i = 0; i < 2; i++)
    {
      Console.WriteLine(objStudent[i] + "\t\t" + objResult[i] + " class");
    }
  }
}

Trong ví dụ trên, lớp Student khai báo một biến mảng và một indexer virtual, lớp Result thừa kế lớp Student và override indexer virtual. Phương thức Main() khai báo một thể hiện của lớp cơ sở Student và một thể hiện của lớp dẫn xuất Result. Khi thể hiện của lớp Student được gán giá trị tại mỗi vị trí chỉ số thì bộ truy cập set của indexer lớp Student được gọi. Khi thể hiện của lớp Result được gán giá trị tại mỗi vị trí chỉ số thì bộ truy cập set của indexer lớp Result được gọi. Ở đây có hiện tượng indexer của lớp dẫn xuất đã ghi đè indexer của lớp cơ sở, cụ thể bộ truy cập set của lớp dẫn xuất Result đã gọi indexer của lớp cơ sở bằng cách sử dụng từ khoá base, và vòng lặp for hiển thị các giá trị tại mỗi vị trí chỉ số bằng cách gọi các bộ truy cập get của các lớp tương ứng. Kết quả của ví dụ được thể hiện ở hình dưới đây.

Indexer nhiều tham số

Indexer bắt buộc phải có tối thiểu một tham số. Ngoài ra, ta có thể tạo indexer với nhiều tham số, khi đó indexer sẽ được truy cập giống như mảng nhiều chiều; những indexer này có thể được dùng để tổ chức một tập các giá trị liên quan, chẳng hạn như nó có thể được dùng để lưu trữ cũng như thay đổi các giá trị đó.

Dưới đây là ví dụ về indexer có hai tham số:

using System;
class Account
{
  string[,] accountDetails = new string[4, 2];
  public string this[int pos, int column]
  {
    get
    {
      return accountDetails[pos, column];
    }
    set
    {
      accountDetails[pos, column] = value;
    }
  }
  static void Main(string[] args)
  {
    Account objAccount = new Account();
    string[] id = new string[3] { "1001", "1002", "1003" };
    string[] name = new string[3] { "Long", "Thanh", "Binh" };
    int counter = 0;
    for (int i = 0; i < 3; i++)
    {
      for (int j = 0; j < 1; j++)
      {
        objAccount[i, j] = id[counter];
        objAccount[i, j + 1] = name[counter++];
      }
    }
    Console.WriteLine("ID   Ten");
    Console.WriteLine();
    for (int i = 0; i < 4; i++)
    {
      for (int j = 0; j < 2; j++)
      {
        Console.Write(objAccount[i, j] + " ");
      }
      Console.WriteLine();
    }
  }
}

Trong ví dụ trên, lớp Account tạo một biến mảng accountDetails gồm 4 hàng và hai cột. Indexer sẽ nhập các giá trị các phần tử của mảng accountDetails. Indexer gồm hai tham số và chúng sẽ định nghĩa các vị trí của các giá trị mà sẽ được lưu trong mảng. Phương thức Main() tạo một thể hiện của lớp Account, thể hiện này được dùng để nhập các giá trị cho mảng accountDetails thông qua vòng lặp for, điều này sẽ gọi đến bộ truy cập set của indexer và set sẽ gán các giá trị cho các phần tử mảng. Vòng lặp for lồng phía dưới sẽ hiển thị ID được lưu trong mảng bằng cách gọi bộ truy cập get.

Sử dụng Indexer trong interface

Trong interface đương nhiên bạn cũng có quyền khai báo indexer, chỉ có lưu ý rằng bạn chỉ được khai báo indexer, trong đó các bộ truy cập của indexer cũng chỉ được là khai báo, không có phần định nghĩa. Mỗi indexer khai báo trong interface cũng mặc định có bổ từ truy cập là public và phải được thực thi  trong lớp thực thi interface đó. Điều này sẽ làm tăng khả năng tái sử dụng và cung cấp một cách linh hoạt các interface tùy chỉnh. Ví dụ:

using System;
public interface IDetails
{
  string this[int index]
  {
    get;
    set;
  }
}
class Students : IDetails
{
  string[] studentName = new string[3];
  int[] studentID = new int[3];
  public string this[int index]
  {
    get
    {
      return studentName[index];
    }
    set
    {
      studentName[index] = value;
    }
  }
  static void Main(string[] args)
  {
    Students objStudent = new Students();
    objStudent[0] = "Phuong";
    objStudent[1] = "Hung";
    objStudent[2] = "Trinh";
    Console.WriteLine("Ten sinh vien:");
    Console.WriteLine();
    for (int i = 0; i < 3; i++)
    {
      Console.WriteLine(objStudent[i]);
    }
  }
}

Trong ví dụ trên, interface IDetails khai báo một indexer read-write. Lớp Student thực thi interface IDetails và thực thi indexer được định nghĩa trong interface. Phương thức Main() tạo một thể hiện của lớp Students và thông qua bộ truy cập set của indexer nó gán các giá trị cho mảng studentName. Thông qua bộ truy cập get của indexer, vòng lặp sẽ in ra màn hình giá trị của các phần tử mảng studentName.

Sự khác nhau giữa Property và Indexer

- Property được gán một tên duy nhất trong quá trình khai báo, còn Indexer không thể có tên, từ khóa this sẽ thay thế khi khai báo.

- Property được gọi thông qua tên của nó, còn Indexer được gọi thông qua chỉ số của thể hiện được tạo.

- Property có thể được khai báo là tĩnh (dùng từ khóa static), Indexer không thể được khai báo là tĩnh.

- Property luôn luôn được khai báo mà không có tham số, Indexer phải được khai báo với ít nhất một tham số.

- Property không thể được nạp chồng, nhưng Indexer có thể được nạp chồng.

- Việc ghi đè property được thực hiện bằng cách sử dụng cú pháp: base.Tên_property. Còn việc ghi đè Indexer được thực hiện bằng cách sử dụng cú pháp: base[Danh_sách_tham_số].

Namespace

Namespace trong C# dùng để nhóm các lớp một cách hợp lý và ngăn ngừa hiện tượng xung đột tên giữa các lớp có tên giống nhau.

Namespace cũng giúp giảm đi tính phức tạp khi một chương trình lại được yêu cầu trong các ứng dụng khác nhau.

Ví dụ, Venice là tên của một thành phố của nước Mỹ, và cũng là tên của một thành phố của nước Ý. Ta có thể dễ dàng phân biệt hai thành phố cùng tên này khi kết hợp với tên của quốc gia tương ứng với tên thành phố, chẳng hạn như Mỹ.Venice hoặc Ý.Venice.

Tương tự như vậy, khi làm việc với một dự án lớn, có thể xảy ra tình huống các lớp có tên giống nhau, và điều này sẽ dẫn đến hiện tượng xung đột tên. Vấn đề này có thể được giải quyết bằng cách tạo các namespace khác nhau trong dự án đó và đưa mỗi lớp có tên giống nhau vào một namespace riêng.

Cú pháp

Ta sử dụng từ khóa namespace để tạo một namespace, cụ thể như sau:

namespace Tên_namespace
{
    //Khai báo các lớp và các thành phần khác
}

Ví dụ, các lớp dưới đây mô tả hai dòng sản phẩm của hai hãng khác nhau:

class TiviSamsung

    ...
}
class SamsungWalkMan

    ...
}
class TiviSony

    ...
}
class SonyWalkMan

    ...
}

Việc đặt tên như ví dụ trên là khá dài và có thể gây khó khăn cho vấn đề bảo trì mã lệnh. Đoạn code dưới đây có thể khắc phục được nhược điểm trên:

namespace Samsung
{
    class Television 
    {
        ...
    }
    class WalkMan
    {
        ... 
    }
}
namespace Sony

    class Television 
    { 
        ... 
    } 
    class Walkman 
    { 
        ... 
    }
}

Sử dụng Namespace

C# cho phép ta tạo một định danh duy nhất cho mỗi namespace, định danh sẽ được dùng để truy cập vào các lớp trong namespace đó.

Ngoài lớp ra thì những cấu trúc dữ liệu sau đây có thể được đặt trong namespace:

· Interface

Interface (Giao diện) là một kiểu tham chiếu, trong interface chứa các khai báo như event, indexer, các phương thức và các property. Interface được thừa kế bởi lớp, cấu trúc, interface khác và tất cả các khai báo trong interface phải được được thực thi trong các lớp cũng như cấu trúc thừa kế nó.

· Structure

Structure (Cấu trúc) là một kiểu giá trị và nó có thể mang những giá trị có kiểu khác nhau, những giá trị này bao gồm trường, phương thức, hằng, property, indexer, operator và các cấu trúc khác.

· Enumeration

Enumeration (Kiểu liệt kê) là kiểu giá trị và nó chứa một danh sách các hằng có tên, các hằng này còn được gọi là danh sách Enumerator.

· Delegate

Delegate là kiểu tham chiếu do người dùng định nghĩa và nó tham chiếu tới một hoặc nhiều phương thức. Nó có thể được dùng để truyền dữ liệu dưới dạng các tham số cho các phương thức.

Đặc điểm và lợi điểm

Namespace sẽ nhóm các lớp, structure và interface có những đặc điểm tương đồng và liên quan đến nhau thành một nhóm và nhóm này đáp ứng được hai đặc điểm của lập trình hướng đối tượng là đóng gói và trừu tượng.

Đặc điểm của namespace:

· Cung cấp cấu trúc phân cấp giúp định danh một cách logic các nhóm lớp.

· Ta có thể thêm nhiều lớp, structure, enumeration, delegate và interface khi khai báo namespace.

· Các lớp trong cùng một namespace phải có tên khác nhau.

Lợi điểm của namespace:

· Cho phép ta sử dụng nhiều lớp có cùng tên bằng cách đặt các lớp này trong các namespace khác nhau.

· Tạo các mô-đun hệ thống.

Namespace System

.NET Frameworks cung cấp một số namespace hệ thống rất hữu ích, trong các namespace này có thể chứa các lớp, interface, structure, delegate và enumeration. Namespace hệ thống phổ biến nhất là System.

System chứa các lớp (trong đó định nghĩa các kiểu dữ liệu giá trị và kiểu dữ liệu tham chiếu), các interface và các namespace khác. System cũng chứa các lớp cho phép ta tương tác được với hệ thống như các thiết bị nhập và xuất dữ liệu. Những namespace phổ dụng nhất trong System bao gồm:

· System.Collections

Namespace này chứa các lớp và interface trong đó định nghĩa các cấu trúc dữ liệu phức tạp như list, queue, bit array, hash table và dictionary.

· System.Data

Namespace chứa các lớp dùng để tạo các kiến trúc ADO.NET, kiến trúc cho phép bạn xây dựng các thành phần được dùng để chèn, sửa và xóa dữ liệu từ các nguồn dữ liệu khác nhau.

· System.Diagnostics

Chứa các lớp dùng để tương tác với các tiến trình hệ thống, nó cũng cung cấp các lớp dùng để gỡ lỗi (debug) các ứng dụng và theo dõi việc thực thi các mã lệnh.

· System.IO

Chứa các lớp cho phép bạn đọc và ghi các luồng và tập tin dữ liệu.

· System.Net

Chứa các lớp cho phép ta  tạo các ứng dụng trên nền web.

· System.Web

Namespace này cung cấp các lớp và interface cho phép giao tiếp giữa trình duyệt và server (máy chủ).

Sử dụng namespace System

System được đưa một cách mặc định vào .NET Framework. System xuất hiện ngay tại dòng đầu tiên của chương trình cùng với từ khóa using đặt trước nó. Để tham chiếu tới các lớp trong một namespace được xây dựng sẵn bạn cần xác định namespace chứa các lớp đó và sử dụng cú pháp sau để khai báo:

using Tên_namespace.Tên_namespace_con;

Lưu ý là vị trí khai báo này phải nằm ở những dòng đầu tiên của chương trình. Ví dụ:

using System.Collections;

Bạn cũng có thể không cần phải khai báo các namespace mà vẫn sử dụng được các thành phần trong nó. Tuy nhiên điều nãy sẽ dẫn đến nhược điểm là câu lệnh sẽ dài và vì vậy dễ dẫn tới rối code.

Ví dụ, nếu bạn sử dụng khai báo namespace thì mã lệnh sẽ như sau:

using System.Collections;
ArrayList arr = new ArrayList();

Còn dưới đây là trường hợp không khai báo namespace:

System.Collections.ArrayList arr = new System.Collections.ArrayList();

Namespace tùy chỉnh

C# cho phép bạn tạo các namespace riêng của bạn gọi là namespace tùy chỉnh, và các namespace này có tác dụng trên tất cả các ứng dụng khác nhau mà bạn sẽ xây dựng. Trong namespace này bạn có quyền tạo tất cả các loại thành phần giống như những loại namespace có sẵn khác.

Lưu ý: Nếu bạn không khai báo bất kỳ namespace nào trong tập tin (file) nguồn thì trình biên dịch sẽ tự động tạo một namespace mặc định không có tên trong file đó, và namespace này được tham chiếu như là một namespace toàn cục (global).

C# cho phép có nhiều namespace cùng tên trong cùng một file hoặc cùng một chương trình, nhưng các thành phần bên trong chúng phải không được trùng tên nhau.

Ví dụ sau đây cho thấy trong cùng một file bạn có thể làm được điều này:

namespace A
{
  class A
  {
  }
}
namespace A
{
  class B
  {
  }
}

Nhưng bạn không được làm như sau:

namespace A
{
  class A
  {
  }
}
namespace A
{
  interface A /* sai, vì thành phần này cũng có tên là A giống ở trên, mặc dù ở trên là class A còn đây là interface A */
  {
  }
}

C# cũng cho phép tách các thành phần của cùng một namespace và đặt ở các file khác nhau, việc này sẽ giúp ta có thể dễ dàng hơn trong vấn đề quản lý code. Ví dụ, ta có namespace sau:

namespace A
{
  class B
  {
  }
  class C
  {
  }
  class D
  {
  }
}

Bây giờ ta sẽ tách namespace A ở trên thành 3 phần và đặt ở 3 file khác nhau:

file thứ nhất (giả sử có tên là B.cs):

namespace A
{
  class B
  {
  }
}

file thứ hai (giả sử có tên là C.cs):

namespace A
{
  class C
  {
  }
}

file thứ ba (giả sử có tên là D.cs):

namespace A
{
  class D
  {
  }
}

Lưu ý khi tạo namespace tùy chỉnh

· Tất cả các thành phần tương tự nhau (class, interface, ...) cần phải được tạo trong một namespace. Điều này sẽ tạo ra một nhóm logic các kiểu tương tự nhau và điều này cũng sẽ giúp tìm kiếm các lớp tương tự nhau một cách dễ dàng.

· Cần tránh việc thiết kế các thành phần nằm quá sâu bên trong các namespace, vì điều này sẽ dẫn đến vấn đề là khó tìm đến được các thành phần sâu bên trong đó.

Ví dụ, ta nên tránh việc làm như sau:

namespace A
{
  namespace B
  {
    namespace C
    {
      namespace D
      {
        class E //việc tìm đến class E trở nên khó khăn
        {
        }
      }
    }
  }
}

· Cần tránh việc tạo quá nhiều namespace dẫn đến khó kiểm soát code. Nói chung cần xây dựng code sao cho càng đơn giản càng tốt.

Bổ từ truy cập cho namespace tùy chỉnh

Mặc định namespace luôn có bổ từ truy cập là public, và bạn sẽ không thể sử dụng bất kỳ bổ từ truy cập nào cho nó (kể cả bổ từ public). Như vậy thì namespace luôn cho phép các thành phần khác nằm ngoài nó truy cập đến nó. Ví dụ:

protected namespace A /*trình biên dịch sẽ báo lỗi vì đã sử dụng bổ từ truy cập cho namespace*/
{
}

Tên chính tắc

Một lớp khi đã được khai báo trong các namespace thì thông thường nếu bạn muốn sử dụng tên lớp này bạn sẽ phải thông qua các namespace bao ngoài nó. Cách sử dụng tên lớp như vậy được gọi là tên chính tắc. Tên chính tắc có nhược điểm là kích thước tên dài và tên sẽ được sử dụng trong toàn bộ chương trình. Ví dụ:

using System;
namespace A
{
  namespace B
  {
    namespace C
    {
      class D
      {
        public void demo()
        {
          Console.WriteLine("A>B>C>D>demo()");
        }
        public void demo1()
        {
          Console.WriteLine("A>B>C>D>demo1()");
        }
      }
    }
  }
}
namespace X
{
  class Y
  {
    void demo()
    {
      A.B.C.D d = new A.B.C.D();
      d.demo();
      A.B.C.D d1 = new A.B.C.D();
      d1.demo1();
    }
  }
 
}

Ở ví dụ trên ta thấy, trong phương thức demo() của lớp Y bạn muốn tạo ra thể hiện d của lớp D thì bạn phải bắt đầu từ namespace đầu tiên (nằm ngoài cùng) rồi đi dần vào trong để tới được lớp D, và bạn phải lặp lại điều này để tạo được thể hiện d1 của lớp D. Bạn có thể khắc phục được nhược điểm này nếu bạn sử dụng tên không chính tắc.

Tên không chính tắc

Sau khi bạn khai báo một lớp nằm trong các namespace, thì bạn vẫn có thể sử dụng tên lớp này một cách trực tiếp mà không cần phải thông qua tên của các namespace bao ngoài nó bằng cách khai báo các namespace thông qua sử dụng từ khóa using, và tên lớp như vậy gọi là tên không chính tắc. Tên không chính tắc có ưu điểm là kích thước tên ngắn và vì vậy góp phần làm chương trình trở nên đơn giản và dễ đọc hơn. Ví dụ dưới đây sẽ khắc phục được nhược điểm của ví dụ ở phần Tên chính tắc:

using System;
using A.B.C;
namespace A
{
  namespace B
  {
    namespace C
    {
      class D
      {
        public void demo()
        {
          Console.WriteLine("A>B>C>D>demo()");
        }
        public void demo1()
        {
          Console.WriteLine("A>B>C>D>demo1()");
        }
      }
    }
  }
}
namespace X
{
  class Y
  {
    void demo()
    {
      D d = new D();
      d.demo();
      D d1 = new D();
      d1.demo1();
    }
  } 
}

Namespace lồng

C# cho phép bạn tạo namespace nằm trong namespace khác theo hướng phân cấp cha>con>cháu>... gọi là namespace lồng (nested namespace). Namespace lồng trở nên hữu dụng khi một tổ chức hay một công ty nào đó vận hành nhiều dự án một lúc; khi đó tên của namespace đầu tiên có thể là tên của tổ chức hoặc công ty, tên của các namespace bên trong có thể là tên của các dự án hoặc môđun khác nhau. Điều này sẽ giúp cho các nhà phát triển lưu trữ các lớp quan trọng trong các namespace một cách hợp lý để các lớp hay các thành phần khác dễ tương tác.

Cú pháp tạo namespace lồng

namespace Tên_namespace1
{
  //khai báo các thành phần
  namespace Tên_namespace2
  {
    namespace Tên_namespace3
    {
      ...
    }
  }
}

Ví dụ:

using System;
namespace LienHe
{
  public class NhanVien
  {
    public int idNhanVien;
  }
  namespace Luong
  {
    public class ChiTietLuong
    {
      public double luongNhanVien;
    }
  }
}
class ChiTietNhanVien
{
  static void Main(string[] args)
  {
    LienHe.Luong.ChiTietLuong objLuong = new LienHe.Luong.ChiTietLuong();
    objLuong.luongNhanVien = 1000.50;
    Console.WriteLine("Luong: " + objLuong.luongNhanVien);
  }
} 

Ví dụ trên cho thấy lớp ChiTietLuong được tạo nằm bên trong hai namespace lồng nhau là LuongLienHe.

Bí danh cho namespace

Bí danh là tên thay thế tạm thời của một thực thể. Namespace được gọi tới với từ khóa using sẽ tham chiếu tới tất cả các thành phần trong namespace đó. Tuy vậy, đôi khi bạn chỉ muốn truy cập đến một thành phần nào đó trong namespace, và vì vậy việc sử dụng bí danh có thể là không cần thiết; nhưng bạn có thể sử dụng bí danh để truy cập dến những thành phần cần thiết mà nhiều khi bắt buộc phải đặt bí danh mới truy cập được, và bí danh cũng có một tác dụng nữa là để tránh việc phải sử dụng tên dài nếu namespace có tên dài hoặc thành phần truy cập nằm sâu bên trong các namespace lồng nhau, và điều này sẽ giúp mã lệnh ngắn gọn và dễ đọc hơn.

Cú pháp tạo bí danh như sau:

using Bí_danh_namespace = Tên_namespace;

Xét đoạn mã sau đây:

using System;
using IO = System.Console;
using NV = NganHang.TaiKhoan.ChiTietNhanVien;
namespace NganHang.TaiKhoan.ChiTietNhanVien
{
  public class NhanVien
  {
    public string tenNhanVien;
  }
}
class ViDuBiDanh
{
  static void Main(string[] args)
  {
    NV.NhanVien objNV = new NV.NhanVien();
    objNV.tenNhanVien = "Long";
    IO.WriteLine("Ten Nhan vien: " + objNV.tenNhanVien);
  }
}

Đoạn mã trên có hai điểm nhấn:

- Thứ nhất là việc đặt bí danh cho namespace NganHang.TaiKhoan.ChiTietNhanVienNV, từ đó việc truy cập đến lớp NhanVien của namespace này trở nên đơn giản và code dễ đọc hơn. Cụ thể, thay vì phải sử dụng tên lớp là Bank.Accounts.EmployeeDetails.Employees, thì ta chỉ cần sử dụng là NV.NhanVien.

- Thứ hai là việc đặt bí danh cho lớp System.ConsoleIO, nếu vậy thì thay vì ta phải viết:

Console.WriteLine(...);

, thì ta chỉ cần viết là:

IO.WriteLine(...);

, và tất cả các thành phần khác của lớp System.Console trong chương trình sẽ đều được truy xuất thông qua bí danh IO (IO.Write(), IO.Read(), IO.ReadLine(), ...).

Con trỏ (Pointer) trong C#

Có những trường hợp ta cần truy xuất bộ nhớ trực tiếp khi ta muốn truy xuất vào các hàm bên ngoài (không thuộc .NET) mà đòi hỏi con trỏ được truyền vào như tham số (ví dụ như các hàm API ), hoặc là vì ta muốn truy nhập vào nội dung bộ nhớ để sửa lỗi, ... Trong phần này ta sẽ xem xét cách C# đáp ứng những điều này như thế nào.

Con trỏ (Pointer)

Con trỏ (pointer) đơn giản là một biến lưu  địa chỉ của một thành phần khác theo cùng một cách như là một tham chiếu. Ở đây có sự khác biệt so với C/C++ là cú pháp C# trong tham chiếu không cho phép ta truy xuất vào địa chỉ bộ nhớ.

Ưu điểm của con trỏ:

· Cải thiện sự thực thi : cho ta biết những gì ta đang làm, đảm bảo rằng dữ liệu được truy xuất hay thao tác theo cách hiệu quả nhất - đó là lí do mà C và C++ cho phép dùng con trỏ  trong ngôn ngữ của mình.

· Khả năng tương thích với các phần trước (Backward compatibility) - đôi khi ta phải sử dụng lại các hàm API cho mục đích của ta. Mà các hàm  API được viết bằng C, ngôn ngữ dùng con trỏ rất nhiều, nghĩa là nhiều hàm lấy con trỏ như tham số, hoặc là các DLL  do một hãng nào đó cung cấp chứa các hàm lấy con trỏ làm tham số . Trong nhiều trường hợp ta có thể viết các khai báo DLlImport theo cách tránh sử dụng con trỏ, ví dụ như dùng lớp System.IntPtr.

· Ta có thể cần tạo ra các địa chỉ vùng nhớ có giá trị cho người dùng - ví dụ nếu ta muốn phát triển một ứng dụng mà cho phép người dùng tương tác trực tiếp đến bộ nhớ, như là một debugger.

Nhược điểm của con trỏ:

· Cú pháp để lấy các hàm phức tạp hơn

· Con trỏ khó sử dụng

· Nếu không cẩn thận ta có thể viết chồng biến, làm tràn stack, mất thông tin, đụng độ, ...

· C# có thể từ chối thi hành những đoạn mã không an toàn này (đoạn mã có sử dụng con trỏ).

Ta có thể đánh dấu đoạn mã có sử dụng con trỏ bằng cách dùng từ khoá unsafe

Ví dụ:

Dùng cho hàm:

unsafe int GetSomeNumber()
{
   // code that can use pointers
}

Dùng cho lớp  hay struct:

unsafe class MyClass
{
   // any method in this class can now use pointers
}

Dùng cho một trường:

class MyClass
{
   unsafe int *pX;   // declaration of a pointer field in a class
}

Dùng cho một khối mã:

void MyMethod()
{
   // code that doesn't use pointers
   unsafe
   {
      // unsafe code that uses pointers here
   }
   // more 'safe' code that doesn't use pointers
}

Tuy nhiên ta không thể đánh dấu 1 biến cục bộ là unsafe:

int MyMethod()
{
   unsafe int *pX;   // WRONG
}

Để biên dịch các mã chứa khối unsafe ta dùng lệnh sau:

csc /unsafe MySource.cs

Hoặc:

csc -unsafe MySource.cs

Cú pháp con trỏ

Kiểu_dữ_liệu* Tên_con_trỏ1, Tên_con_trỏ1, ...;

Ví dụ:

int* pWidth, pHeight; //khai báo hai con trỏ pWidth và pHeight có cùng kiểu int

double *pResult; //khai báo con trỏ pResult có kiểu double

Cách dùng con trỏ:

Tên_con_trỏ: chứa địa chỉ

*Tên_con_trỏ : chứa giá trị (nội dung)

Ví dụ:

int x = 10;

int* pX, pY; //khai báo hai con trỏ pX và pY

pX = &x; //cho con trỏ pX trỏ tới biến x (pX sẽ chứa địa chỉ của biến x)

pY = pX; //cho con trỏ pY cũng trỏ tới biến x

*pY = 20; //gán 20 cho biến x

uint y = (uint)pX; //ép kiểu (tường minh) sang kiểu uint

int* pD = (int*)y; //ép kiểu sang kiểu int rồi gán cho pD

y là uint. Sau đó ta chuyển ngược lại thành biến con trỏ pD.

Một lý do để ta phải ép kiểu là Console.WriteLine không có overload nào nhận thông số là con trỏ do đó ta phải ép nó sang kiểu số nguyên int.

Console.WriteLine("Address is" + pX);   // wrong - will give a compilation error

Console.WriteLine("Address is" + (uint) pX);   // OK

Ép kiểu giữa những kiểu con trỏ 

Ta cũng có thể chuyển đổi tường minh giữa các con trỏ trỏ đến1 kiểu khác ví dụ :

byte aByte = 8;
byte *pByte= &aByte;
double *pDouble = (double*)pByte;
 

void Pointers

Nếu ta muốn giữ 1 con trỏ , nhưng không muốn đặc tả kiểu cho con trỏ ta có thể khai báo co ntrỏ là void:

void *pointerToVoid;
pointerToVoid = (void*)pointerToInt;   // pointerToInt declared as int*

mục đích là khi ta cần gọi các hàm API mà đòi hỏi thông số void*.

Toán tử sizeof 

Lấy thông số là tên của kiểu và trả về số byte của kiểu đó ví dụ :

int x = sizeof(double);

x có giá trị là 8.

Bảng kích thước kiểu :

sizeof(sbyte) = 1;

sizeof(byte) = 1;

sizeof(short) = 2;

sizeof(ushort) = 2;

sizeof(int) = 4;

sizeof(uint) = 4;

sizeof(long) = 8;

sizeof(ulong) = 8;

sizeof(char) = 2;

sizeof(float) = 4;

sizeof(double) = 8;

sizeof(bool) = 1;

Ta cũng có thể dùng sizeof cho struct nhưng không dùng được cho lớp.

Ví dụ sau trình bày cách thao tác trên con trỏ và trình bày kết quả, cho phép ta thấy những gì xảy ra trong bộ nhớ và nơi biến được lưu trữ:

using System;

namespace Wrox.ProCSharp.AdvancedCSharp
{
   class MainEntryPoint
   {
      static unsafe void Main()
      {
         int x=10;
         short y = -1;
         byte y2 = 4;
         double z = 1.5;
         int *pX = &x;
         short *pY = &y;
         double *pZ = &z;

         Console.WriteLine(
            "Address of x is 0x{0:X}, size is {1}, value is {2}",
            (uint)&x, sizeof(int), x);
         Console.WriteLine(
            "Address of y is 0x{0:X}, size is {1}, value is {2}",
            (uint)&y, sizeof(short), y);
         Console.WriteLine(
            "Address of y2 is 0x{0:X}, size is {1}, value is {2}",
            (uint)&y2, sizeof(byte), y2);
         Console.WriteLine(
            "Address of z is 0x{0:X}, size is {1}, value is {2}",
            (uint)&z, sizeof(double), z);
         Console.WriteLine(
            "Address of pX=&x is 0x{0:X}, size is {1}, value is 0x{2:X}",
            (uint)&pX, sizeof(int*), (uint)pX);
         Console.WriteLine(
            "Address of pY=&y is 0x{0:X}, size is {1}, value is 0x{2:X}",
            (uint)&pY, sizeof(short*), (uint)pY);
         Console.WriteLine(
            "Address of pZ=&z is 0x{0:X}, size is {1}, value is 0x{2:X}",
            (uint)&pZ, sizeof(double*), (uint)pZ);

         *pX = 20;
         Console.WriteLine("After setting *pX, x = {0}", x);
         Console.WriteLine("*pX = {0}", *pX);

         pZ = (double*)pX;
         Console.WriteLine("x treated as a double = {0}", *pZ);

         Console.ReadLine();
      }
   }
}

Mã gồm 3 biến 

· int x

· short y

· double z

Cùng với các con trỏ trỏ đến các giá trị này.sau đó ta trình bày giá trị của các biến và kích thước,địa chỉ của nó.Ta dùng đặc tả {0:X} trong Console.WriteLine để  địa chỉ  bộ nhớ được trình bày theo định dạng số bát phân.

Cuối cùng ta dùng con trỏ pX thay đổi giá trị của x thành 20, và thử ép kiểu biến x thành 1 double để xem điều gì sẻ xảy ra.

Biên dịch mã, ta có kết quả sau :
csc PointerPlayaround.cs
Microsoft (R) Visual C# .NET Compiler version 7.00.9466
for Microsoft (R) .NET Framework version 1.0.3705
Copyright (C) Microsoft Corporation 2001. All rights reserved.

PointerPlayaround.cs(7,26): error CS0227: Unsafe code may only appear if
        compiling with /unsafe

csc /unsafe PointerPlayaround.cs
Microsoft (R) Visual C# .NET Compiler version 7.00.9466
for Microsoft (R) .NET Framework version 1.0.3705
Copyright (C) Microsoft Corporation 2001. All rights reserved.


PointerPlayaround
Address of x is 0x12F8C4, size is 4, value is 10
Address of y is 0x12F8C0, size is 2, value is -1
Address of y2 is 0x12F8BC, size is 1, value is 4
Address of z is 0x12F8B4, size is 8, value is 1.5
Address of pX=&x is 0x12F8B0, size is 4, value is 0x12F8C4
Address of pY=&y is 0x12F8AC, size is 4, value is 0x12F8C0
Address of pZ=&z is 0x12F8A8, size is 4, value is 0x12F8B4
After setting *pX, x = 20
*pX = 20
x treated as a double = 2.63837073472194E-308

Pointer Arithmetic

Ta có thể cộng hay trừ số nguyên trên con trỏ.Ví dụ , giả sử ta có 1 con trỏ trỏ đến số nguyên,và ta thử cộng 1 vào giá trị của nó .trình biên dịch sẽ biết và tăng vùng nhớ lên 4 byte ( do kiểu int có kích thước 4 byte).nếu là kiểu double thì khi cộng 1 sẽ tăng giá trị của con trỏ lên 8 byte.

Ta có thể dùng toán tử +, -, +=, -=, ++,và -- với biến bên phía phải của toán tử này là long hay ulong.

Ví dụ

uint u = 3;
byte b = 8;
double d = 10.0;
uint *pUint= &u;        // size of a uint is 4
byte *pByte = &b;       // size of a byte is 1
double *pDouble = &d;   // size of a double is 8

Giả sử địa chỉ của những con trỏ này trỏ đến là :

· pUint: 1243332

· pByte: 1243328

· pDouble: 1243320

Sau khi thi hành ta có :

++pUint;              // adds 1= 4 bytes to pUint
pByte -= 3;           // subtracts 3=3bytes from pByte
double *pDouble2 = pDouble - 4; // pDouble2 = pDouble - 32 bytes (4*8 bytes)

Con trỏ sẽ có giá trị:

· pUint: 1243336

· pByte: 1243321

· pDouble2: 1243328

Ta cũng có thể trừ 2 con trỏ với nhau .giá trị kết quả là kiểu long bằng giá trị con trỏ chia cho kích thước của kiểu mà nó đại diện .Ví dụ :

double *pD1 = (double*)1243324;   // note that it is perfectly valid to
                                  // initialize a pointer like this.
double *pD2 = (double*)1243300;
long L = pD1-pD2;                 // gives the result 3 (=24/sizeof(double))

Con trỏ đến Struct - Toán tử truy xuất các thành viên con trỏ

Cũng giống như con trỏ trong các kiểu dữ liệu có sẵn. tuy nhiên thêm 1 điều kiện là - Struct không chứa bất kì kiểu tham chiếu nào.Do con trỏ không thể trỏ đến bất kì kiểu tham chiếu nào. để tránh điều này , trình biên dịch sẽ phất cờ lỗi nếu ta tạo ra một con trỏ đến bất kì Struct nào chứa kiểu tham chiếu .

Giả sử ta có struct như sau :

struct MyGroovyStruct
{
   public long X;
   public float F;
}

Sau đó ta định nghĩa con trỏ cho nó :

MyGroovyStruct *pStruct;

Khởi tạo nó :

MyGroovyStruct Struct = new MyGroovyStruct();

pStruct = &Struct;

Cũng có thể truy xuất các giá trị thành viên của 1 struct bằng con trỏ :

(*pStruct).X = 4;

(*pStruct).F = 3.4f;

Tuy nhiên cú pháp này hơi phức tạp. C# định nghĩa 1 toán tử khác cho phép ta truy xuất các thành viên của Struct bằng con trỏ đơn giản hơn , gọi là toán tử truy xuất thành viên con trỏ ,kí hiệu là  ->

Cách dùng :

pStruct->X = 4;
pStruct->F = 3.4f;

Ta cũng có thể thiết đặt trực tiếp con trỏ của kiểu tương đương để trỏ đến các  trường trong Struct

long *pL = &(Struct.X);

float *pF = &(Struct.F);

hay :

long *pL = &(pStruct->X);

float *pF = &(pStruct->F);

Con trỏ đến các thành viên của lớp 

Ta đã nói rằng không thể tạo ra con trỏ đến lớp vì việc tạo có thể làm cho bộ gom rác hoạt động không đúng. 

Tuy nhiên ta có thể tạo các con trỏ đến các thành viên của lớp. Ta sẽ viết lại struct của ví dụ trước như là lớp :

class MyGroovyClass
{
   public long X;
   public float F;

sau đó ta có thể tạo 1 con trỏ đến các trường của nó ,X và F.tuy nhiên làm như vậy sẽ gây ra lỗi :

MyGroovyClass myGroovyObject = new MyGroovyClass();

long *pL = &( myGroovyObject.X);   // wrong

float *pF = &( myGroovyObject.F);  // wrong

Do X và F nằm trong 1 lớp , mà được đặt trong heap.nghĩa là chúng vẫn gián tiếp chịu sự quản lý của bộ gom rác.cụ thể bộ gom rác có thể quyết định di chuyển MyGroovyClass đến 1 vị trí mới trong bộ nhớ để dọn dẹp heap.Nếu làm điều này thì bộ gom rác tất nhiên sẽ cập nhật tất cả các tham chiếu đến đối tượng ,giả sử như biến myGrooveObject  vẫn sẽ trỏ đến đúng vị trí.Tuy nhiên bộ gom rác không biết gì về con trỏ cả. vì thế nếu di chuyển các đối tượng tham chiếu bởi myGrooveObject,pL và pF sẽ vẫn không thay đôỉ  và kết cuộc là trỏ đến sai vị trí vùng nhớ.

Để giải quyết vấn đề này ta dùng từ khóa fixed , mà cho bộ gom rác biết rằng có thể có con trỏ trỏ đến các thành viên của các thể hiện lớp,vì thế các thể hiện lớp này sẽ không được di chuyển.cú pháp như sau nếu ta chỉ muốn khai báo 1 con trỏ :

MyGroovyClass myGroovyObject = new MyGroovyClass();
// do whatever
fixed (long *pObject = &( myGroovyObject.X))
{
   // do something
}

nếu ta muốn khai báo nhiều hơn 1 con trỏ ta có thể đặt nhiều câu lệnh fixed trước  khối mã giống nhau :

MyGroovyClass myGroovyObject = new MyGroovyClass();
fixed (long *pX = &( myGroovyObject.X))
fixed (float *pF = &( myGroovyObject.F))
{
   // do something
}

Ta có thể lồng các khối fixed nếu ta muốn fix các con trỏ trong các  thời điểm khác nhau

MyGroovyClass myGroovyObject = new MyGroovyClass();

fixed (long *pX = &( myGroovyObject.X))

{

   // do something with pX

   fixed (float *pF = &( myGroovyObject.F))

   {

      // do something else with pF

   }

}

Ta cũng có thể khởi tạo vài biến trong cùng 1 khối fixed :

MyGroovyClass myGroovyObject = new MyGroovyClass();

MyGroovyClass myGroovyObject2 = new MyGroovyClass();

fixed (long *pX = &( myGroovyObject.X), pX2 = &( myGroovyObject2.X))

{

   // etc.

}

Thêm các lớp và Struct đến ví dụ

Trong phần này ta sẽ minh họa việc tính toán trên  con trỏ và các con trỏ đến struct và lớp .Ta  dùng ví dụ 2, PointerPlayaround2:

   struct CurrencyStruct
   {
      public long Dollars;
      public byte Cents;

      public override string ToString()
      {
         return "$" + Dollars + "." + Cents;
      }
   }

   class CurrencyClass
   {
      public long Dollars;
      public byte Cents;

      public override string ToString()
      {
         return "$" + Dollars + "." + Cents;
      }
   }

Bây giờ ta có thể áp dụng con trỏ cho các struct và lớp của ta .ta bắt đầu bằng việc trình bày kích thước của stuct , tạo ra 1 vài thể hiện của nó cùng với con trỏ.ta dùng những con trỏ này để khởi tạo 1 trong những struct Currency ,amount1. và trình  bày các địa chỉ của các biến :

public static unsafe void Main()
{
   Console.WriteLine(
      "Size of Currency struct is " + sizeof(CurrencyStruct));
   CurrencyStruct amount1, amount2;
   CurrencyStruct *pAmount = &amount1;
   long *pDollars = &(pAmount->Dollars);
   byte *pCents = &(pAmount->Cents);

   Console.WriteLine("Address of amount1 is 0x{0:X}", (uint)&amount1);
   Console.WriteLine("Address of amount2 is 0x{0:X}", (uint)&amount2);
   Console.WriteLine("Address of pAmt is 0x{0:X}", (uint)&pAmount);
   Console.WriteLine("Address of pDollars is 0x{0:X}", (uint)&pDollars);
   Console.WriteLine("Address of pCents is 0x{0:X}", (uint)&pCents);
   pAmount->Dollars = 20;
   *pCents = 50;
   Console.WriteLine("amount1 contains " + amount1);

Ta biết rằng amount2 sẽ được lưu trữ ở 1 địa chỉ ngay sau amount1, sizeof ( CurrencyStru) trả về 16, vì vậy CurrencyStruct sẽ nằm ở địa chỉ là bội số của 4 byte.do đó sau khi giảm con trỏ currency , nó sẽ trỏ đến amount2:

   --pAmount;   // this should get it to point to amount2
   Console.WriteLine("amount2 has address 0x{0:X} and contains {1}",
      (uint)pAmount, *pAmount);

Ta trình bày nội dụng của amount2 nhưng chưa khởi tạo nó .Dù trình biên dịch C# ngăn không cho chúng ta dùng các giá trị chưa được khởi tạo nhưng khi dùng con trỏ thì điều này không còn đúng nửa.trình biên dịch không cách nào biết nội của amount2 mà ta trình bày, chỉ có ta biết.

kết tiếp ta sẽ tính toán trên con trỏ pCents,pCents hiện thời trỏ đến amount1.Cents , nhưng mục đích của ta là làm cho nó trỏ đến amount2.Cents .Làm điều này ta cần giảm địa chỉ của nó.ta cần làm một vài ép kiểu  :

   // do some clever casting to get pCents to point to cents
   // inside amount2
   CurrencyStruct *pTempCurrency = (CurrencyStruct*)pCents;
   pCents = (byte*) ( --pTempCurrency );
   Console.WriteLine("Address of pCents is now 0x{0:X}", (uint)&pCents);

Cuối cùng ta dùng vài từ khoá fixed để tạo ra một vài con trỏ mà trỏ đến các trường trong thể hiện lớp,và dùng những con trỏ này để thiết đặt giá trị của thể hiện này.Lưu ý rằng điều này cũng là lần đầu tiên ta thấy địa chỉ của một mục được lưu trữ trên heap hơn là trên stack:

   Console.WriteLine("\nNow with classes");
   // now try it out with classes
   CurrencyClass amount3 = new CurrencyClass();

   fixed(long *pDollars2 = &(amount3.Dollars))
   fixed(byte *pCents2 = &(amount3.Cents))
   {
      Console.WriteLine(
         "amount3.Dollars has address 0x{0:X}", (uint)pDollars2);
      Console.WriteLine(
         "amount3.Cents has address 0x{0:X}", (uint) pCents2);
      *pDollars2 = -100;
      Console.WriteLine("amount3 contains " + amount3);
   }

chạy chương trình ta có :

csc /unsafe PointerPlayaround2.cs
Microsoft (R) Visual C# .NET Compiler version 7.00.9466
for Microsoft (R) .NET Framework version 1.0.3705
Copyright (C) Microsoft Corporation 2001. All rights reserved.

PointerPlayaround2
Size of Currency struct is 16
Address of amount1 is 0x12F8A8
Address of amount2 is 0x12F898
Address of pAmt is 0x12F894
Address of pDollars is 0x12F890
Address of pCents is 0x12F88C
amount1 contains $20.50
amount2 has address 0x12F898 and contains $5340121818976080.102
Address of pCents is now 0x12F88C

Now with classes
amount3.Dollars has address 0xBA4960
amount3.Cents has address 0xBA4968
amount3 contains $-100.0

Dùng con trỏ để tối ưu hoá thực thi

Sau đây ta sẽ áp dụng những hiểu biết về con trỏ và minh họa 1 ví dụ mà ta  thấy rõ lợi ích của việc dùng con trỏ trong thực thi

Tạo ra mảng có nền là Stack

Để tạo ra mảng này ta cần từ khoá  stackalloc. lệnh stackalloc chỉ dẫn thời gian chạy .NET để cấp phát 1 số vùng nhớ trên stack khi ta gọi nó ,ta cần cung cấp cho nó 2 thông tin

· Kiểu của biến mà ta muốn lưu trữ

· Ta cần lưu bao nhiêu biến

trong ví dụ , để cấp  phát đủ vùng nhớ lưu trữ 10 số thập phân decimal , ta viết :

decimal *pDecimals = stackalloc decimal [10];

lệnh này chỉ đơn giản cấp phát vùng nhớ. không khởi tạo bất kì giá trị nào.

Để lưu  20 số double ta viết :

double *pDoubles = stackalloc double [20];

mặc dù dòng mã này đặc tả số biến được lưu là hằng, điều này có thể  là 1 định giá số lượng vào lúc chạy. vì thế ta có thể viết tương đương với ví dụ trên như sau :

int size;
size = 20;   // or some other value calculated at run-time
double *pDoubles = stackalloc double [size];

Kiểu mảng cơ bản nhất mà có thể có là 1 khối bộ nhớ lưu các phần tử như sau :

CSharp: Simple Array

Câu hỏi được đặt ra là làm thế nào ta sử dụng vùng nhớ mà ta vừa tạo.trở lại ví dụ ta vừa nói rằng giá trị trả về từ stackalloc  trỏ đến bắt đầu của vùng nhớ.do đó  cho phép ta có thể lấy vị trí đầu tiên của vùng nhớ được cấp phát.ví dụ để cấp phát các số double  và thiết lập phần tử đầu tiên (phần tử 0 của mảng) giá trị 3.0 ta có thể viết :

double *pDoubles = stackalloc double [20];
*pDoubles = 3.0;

Ta có thể thiết lập phần tử thứ 2 của mảng bằng cách dùng cách tính toán trên con trỏ mà ta đã biết .Ví dụ nếu ta muốn đặt giá trị của phần tử thứ hai  ta làm như sau :

double *pDoubles = stackalloc double [20];
*pDoubles = 3.0;
*(pDoubles+1) = 8.4;

Nó chung ta có thể lấy phần tử thứ X của mảng với biểu thức *(pDoubles+X).

Bên cạnh đó C# cũng định nghĩa 1 cú pháp thay thế .Nếu p là con trỏ và X là kiểu số thì biểu thức p[X] tương đương với *(p+X).

double *pDoubles = stackalloc double [20];
pDoubles[0] = 3.0;   // pDoubles[0] is the same as *pDoubles
pDoubles[1] = 8.4;   // pDoubles[1] is the same as *(pDoubles+1)

Mặc dù mảng của ta có thể được truy xuất theo cùng cách như mảng bình thường, ta cần quan tâm đến cảnh báo sau . đoạn mã sau đây sẽ gây ra 1 biệt lệ:

double [] myDoubleArray = new double [20];
myDoubleArray[50] = 3.0;

Biệt lệ xuất hiện vì ta cố truy xuất vào mảng dùng chỉ mục vượt quá mảng ( chỉ mục là 50 , nhưng giá trị lớn nhất cho phép là 19).Tuy nhiên ,nếu ta khai báo 1 mảng dùng stackalloc , điều đó sẽ không gây ra biệt lệ:

double *pDoubles = stackalloc double [20];
pDoubles[50] = 3.0;

Ví dụ

Ta sẽ thảo luận về con trỏ với stackalloc trong ví dụ QuickArray. ví dụ hỏi  người dùng  bao nhiêu phần tử họ muốn cấp phát cho mảng. sau đó ta dùng stackalloc để cấp phát mảng với độ dài đó.các phần tử của mảng này được gán giá trị là bình phương của chỉ mục của nó .kết quả trình bày bên dưới :

using System;

namespace Wrox.ProCSharp.AdvancedCSharp
{
   class MainEntryPoint
   {
      static unsafe void Main()
      {
         Console.Write("How big an array do you want? \n> ");
         string userInput = Console.ReadLine();
         uint size = uint.Parse(userInput);

         long *pArray = stackalloc long [(int)size];
         for (int i=0 ; i<size ; i++)
            pArray[i] = i*i;

         for (int i=0 ; i<size ; i++)
            Console.WriteLine("Element {0} = {1}", i, *(pArray+i));
      }
   }
}

Kết quả:

How big an array do you want?
15
Element 0 = 0
Element 1 = 1
Element 2 = 4
Element 3 = 9
Element 4 = 16
Element 5 = 25
Element 6 = 36
Element 7 = 49
Element 8 = 64
Element 9 = 81
Element 10 = 100
Element 11 = 121
Element 12 = 144
Element 13 = 169
Element 14 = 196

Practical 3

EXERCISE 1: ANIMAL MANAGEMENT SYSTEM

1. Design an interface IAnimal has some Properties:

int ID

string Name

int Age

2. Design interface ITerrestrialAnimal and IMarineAnimal inherit interface IAnimal , both interfaces have following method:

void Move( )

3. Design class Cat, Fish, Crocodile with following instructions:

- Class Cat inherits interface ITerestrial

- Class Fish inherits interface IMarineAnimal

- Class Crocodile inherits interface Iterestrial and IMarineAnimal

- Override method ToString( ) of three classes to print : Animal Type, Animal ID, Name, Age

- Rewrite method Move( ) of interface ITerestrialAnimal to print : Run

- Rewrite method Move( ) of interface IMarineAnimal to print : Swim

Tips: display Animal Type by using method GetType()

4. Build the menu of program:

   1. Create a Crocodile
   2. Create a Cat
   3. Create a Fish
   4. View Terrestrial Animals
   5. View Marine Animals
   6. View All Animals
   7. Delete Animal
   8. Exit

-When user choose 1, 2 or 3: create new animal with its ID increments automatically. ( ID ++). Afterthat, add the animal to an array  IAnimal[] arr.

-When user choose 4: iterate all terrestrial animal in the array, call their methods ToString and Move

-When user choose 5: iterate all marine animal in the array, call their methods ToString and Move

-When user choose 6: iterate all animal in the array, call their methods ToString and Move

-When user choose 7: delete the animal in array that its ID equals the specified number.


EXERCISE 2: NEWS MANAGEMENT SYSTEM

1. Create an interface name INews  contains these properties and methods:

a. Properties

ID (int)

Title(string)  

PublishDate (DateTime)  

Author (string)

Content (string)

AverageRate(float) – Read only property

b. Methods

void Display()

2. Create a class name News:

a. Implements the INews interface in step 1

b. The method Display will print all Title, PublishDate, Author, Content and AverageRate of the news to the console

c. Declare an array name RateList type int has size of 3 elements

d. Create an indexer uses the array RateList in step 2c.

e. Create a method named Calculate to set AverageRate = average of 3 int elements in RateList array.

3. Display  a tasks menu to choose:

   1. Insert news
   2. View list news
   3. Average rate
   4. Exit

4. If user type 1 from keyboard then:
a. Create a new News  instance and input Title, PublishDate, Author, Content and then ask user to enter 3 rates and set to the instance indexer

- The ID is auto increament ( ID++ )

- Be sure to check format of PublishDate

b. Create a Hastable (create only one Hastable only) to keep the News instance in step a with key = ID and value = the news instance.

5. If user type 2 from keyboard then:

Loop from all news instances in the Hastable then executes the Display method from INew interface.

6. If user type 3 from keyboard then:

Loop from all news instances in the Hastable then executes the Calculate method and then execute the Display method.

7. While user not chooses Exit (type 4 from keyboard) then go back to the menu step 3 to ask user chooses an option.

 

Practical 4

EXERCISE 1: STUDENTS MANAGEMENT SYSTEM

In this exam, you’ll have to create a Students Management system. The system allows input, list, search students.

1. Create an interface name IStudent contains these properties and methods:

a. Properties:

- FullName (string)

- ID (int)

- DateofBirth (DateTime)

- Native (string)

- Class (string)

- PhoneNo (string)

- Mobile (int)

b. Methods:

void Display()

2. Create a class name Student:

a. Implements the IStudent interface in step 1.

b. The method Display() will print all FullName, ID, DateofBirth, Native, Class, PhoneNo and Mobile of the student to the console.

3. Display  a tasks menu to choose:

   1. Insert new Student

   2. View list of Students

   3. Search Students

   4. Exit

4. If users type 1 from the keyboard then:

a. Create a new Student instance and inputs FullName, DateofBirth, Native, Class, PhoneNo, and Mobile. The ID is auto increament (++ID ).

b. Create a Hastable (create only one Hastable only) to keep the Student instance in step 4a with key = ID and value = the student instance.

5. If users type 2 from the keyboard then:

Loop from all Student instances in the Hastable then executes the Display() method from IStudent interface.

6. If users type 3 from the keyboard then:

Loop from all instances of Student class in the Hastable then search students of class that is inputed from the keyboard and then executes the Display() method.

7. While user does not choose Exit (type 4 from the keyboard) then go back to the menu step 3 to ask user chooses an option.


EXERCISE 2: MARKS MANAGEMENT SYSTEM

1. Create an interface name IStudentMark  contains these properties and methods:

a. Properties:

- FullName (string)

- ID (int)

- Class (string)

- Semester (int)

- AverageMark (float) – Read only property

b. Methods:

void Display()

2. Create a class name StudentMark:

a. Implements the IStudentMark interface in step 1.

b. The method Display() will print all FullName, ID, Class, Semester, AverageMark.

c. Declare an array name SubjectMarkList type int has size of 5 elements.

d. Create an indexer uses the array SubjectMarkList in step 2c.

e. Create a method named AveCal to set AverageMark = average of 5 int elements in SubjectMarkList array.

3. Display a tasks menu to choose:

menu-marks-management-system

4. If user type 1 from the keyboard then:

a. Create a new StudentMark  instance and input FullName, Class, Semester and then ask users to enter 5 subjectmarks and set to the instance indexer. The ID is auto increament (++ID ).

insert-marks-management-system

b. Create a Hastable (create only one Hastable only) to keep the StudentMark instance in step 4a with key = ID and value = the student instance.

5. If user type 2 from the keyboard then:

Loop from all studentmark instances in the Hastable then executes the Display() method from IStudentMark interface.

6. If user type 3 from the keyboard then:

Loop from all studentmark instances in the Hastable then executes the AveCal() method and then executes the Display() method.

7. While user not chooses Exit (type 4 from the keyboard) then go back to the menu step 3 to ask user chooses an option.

 

Delegate

Tổng quan

Trong .NET Framework, một delegate trỏ tới một hoặc nhiều phương thức. Mỗi khi ta khởi tạo delegate, các phương thức tương ứng sẽ được gọi.

Delegate là đối tượng chứa những tham chiếu tới các phương thức cần được gọi thay vì phải gọi trực tiếp phương thức qua tên của nó. Để sử dụng delegate ta cần gọi một phương thức nào đó và nó được định danh khi thực thi chương trình. Mỗi delegate nói chung cần có một phương thức. Mỗi delegate được coi như một phương thức chung, phương thức chung này trỏ tới các phương thức khác nhau tại những thời điểm khác nhau và tại mỗi thời điểm sẽ gọi một phương thức cụ thể theo yêu cầu của chương trình. Khi ta gọi một delegate thì điều này tương ứng với việc gọi một phương thức được tham chiếu tới.

Để kết hợp delegate với một phương thức cụ thể thì phương thức đó phải có cùng kiểu trả về cũng như số lượng và kiểu tham số với delegate.

Delegate trong C#

Xét hai phương thức là Addition() và Subtract(), phương thức Addition() có hai tham số kiểu int và trả về tổng của hai tham số đó. Tương tự như vậy, phương thức Subtract() cũng có hai tham số kiểu int và trả về hiệu của hai tham số.

Vì cả hai phương thức trên đều có cùng số lượng tham số và kiểu trả về, nên ta tạo một delegate tên Calculation để tham chiếu tới hai phương thức Addition() và Subtract(). Tuy vậy, nếu delegate được gọi để nó trỏ tới phương thức Addition() thì nó phải thực hiện được phép tính tổng hai tham số; và tương tự như thế, nếu ta gọi delegate để nó trỏ tới phương thức Subtract() thì nó phải thực hiện được phép trừ hai tham số.

Delegate trong C# có một số đặc điểm khác biệt với những phương thức thông thường, những đặc điểm này gồm:

· Các phương thức có thể được truyền như là những tham số tới delegate. Ngoài ra, một delegate có thể chấp nhận tham số là một khối lệnh, khối lệnh như vậy sẽ được tham chiếu tới như một phương thức nặc danh.

· Mỗi delegate có thể gọi tới nhiều phương thức cùng một lúc, điều này gọi là multicasting.

· Delegate có thể đóng gói được các phương thức tĩnh.

· Delegate đảm bảo an toàn kiểu (kiểu trả về cũng như kiểu của các tham số) giữa nó và các phương thức được tham chiếu. Điểu này đảm bảo tính bảo mật cũng như độ chính xác cho các dữ liệu khi chúng được truyền tới phương thức được gọi.

Khai báo delegate

Để khai báo delegate trong C# ta dùng từ khóa delegate theo cú pháp như sau:

Bổ_từ_truy_cập delegate Kiểu_trả_về Tên_delegate(Danh_sách_đối_số);

Ví dụ:

public delegate int Calculation(int numOne, int numTwo);

Lưu ý: Nếu bạn khai báo delegate nằm ngoài các lớp thì bạn không thể khai báo thêm delegate có cùng tên và nằm trong cùng namespace.

Tạo thể hiện cho delegate

Sau khi đã khai báo xong delegate thì bạn có thể tạo thể hiện của nó. Cũng giống như những đối tượng khác, khi muốn tạo một thể hiện cho một delegate bạn cần sử dụng từ khóa new, tham số của thể hiện sẽ là phương thức mà delegate muốn tham chiếu tới, và khi thực thi chương trình thì thể hiện của delegate sẽ được dùng để gọi phương tương ứng.

Cú pháp:

Tên_delegate Tên_thể_hiện = new Tên_delegate(Tên_phương_thức);

Ví dụ:

using System;
public delegate int Calculation(int numOne, int numTwo);
class Mathematics
{
  static int Addition(int numOne, int numTwo)
  {
    return (numOne + numTwo);
  }
  static int Subtraction(int numOne, int numTwo)
  {
    return (numOne - numTwo);
  }
  static void Main(string[] args)
  {
    int valOne = 5;
    int valTwo = 23;
    Calculation objCalculation = new Calculation(Addition);
    Console.WriteLine(valOne + " + " + valTwo + " = " + objCalculation(valOne, valTwo));
    objCalculation = new Calculation(Subtraction);
    Console.WriteLine(valOne + " - " + valTwo + " = " + objCalculation(valOne, valTwo));
  }
}

Trong ví dụ trên, delegate Calculation được tạo nằm ngoài lớp Mathematics. Trong phương thức Main() tạo hai đối tượng (thể hiện) của delegate với tham số tương ứng cho mỗi đối tượng là phương thức Addition và phương thức Subtraction. Kiểu tham số của mỗi phương thức và delegate đều cùng kiểu là kiểu int.

Lưu ý: Với phiên bản C# hiện tại, các phương thức không tên gọi là biểu thức 'lambda' được dùng để tạo delegate.

Sử dụng delegate

Ta có thể khai báo delegate nằm ngoài hoặc nằm trong một lớp. Có bốn bước để thực thi một delegate, gồm:

  1. Khai báo delegate.
  2. Tạo phương thức để delegate tham chiếu tới.
  3. Tạo thể hiện (đối tượng) của delegate.
  4. Gọi phương thức thông qua thể hiện của delegate được tạo ở bước 3.

Phương thức nặc danh bản chất là một khối lệnh không có tên, thay vào đó là một delegate. Sử dụng phương thức nặc danh ta sẽ tránh được việc phải tạo tên cho phương thức. Ví dụ:

using System;
class Demo
{
  static void Main(string[] args)
  {
    System.Threading.Thread objThread = new System.Threading.Thread(
      delegate()
      {
        Console.WriteLine("Anonymous Method!");
      }
    );
    objThread.Start();
  }
}

Mô hình Delegate-Event

Mô hình delegate-event là một trong những mô hình lập trình cho phép người dùng tương tác được với máy tính và các thiết bị điều khiển máy tính sử dụng giao diện người dùng đồ họa (Graphical User Interface - GUI). Mô hình này bao gồm:

- Một nguồn sự kiện, nguồn này sẽ là window console trong trường hợp các ứng dụng là dạng console (Console Application).

- Các bộ Listener ('lắng nghe') dùng để nhận các sự kiện từ nguồn sự kiện.

- Một phương tiện (medium) để cung cấp các giao thức cần thiết cho mỗi sự kiện mỗi khi nó được truyền đi.

Theo mô hình trên, mỗi bộ listener có nhiệm vụ phải thực thi một phương tiện ứng với mỗi sự kiện mà nó muốn lắng nghe, khi đó phương tiện sẽ được dùng đến mỗi khi nguồn sự kiện tạo ra một sự kiện, và sự kiện sẽ được thông báo tới bộ listener đã được đăng ký.

Trong C#, mỗi sự kiện là một hành động được tạo ra để kích hoạt phản ứng. Ví dụ, khi nhấn tổ hợp phím Ctrl+Break trên cửa sổ máy chủ dựa trên giao diện điều khiển (console) thì đó là một sự kiện mà sẽ tạo ra hành động kích hoạt máy chủ kết thúc; điều này sẽ gây ra phản ứng kích hoạt dẫn đến thông tin sẽ được lưu vào cơ sở dữ liệu.

Delegate có thể được sử dụng để xử lý các sự kiện; lúc này, delegate sẽ đóng vai trò là tham số và nó có nhiệm vụ mang theo các phương thức được gọi khi xảy ra sự kiện, và những phương thức này được tham chiếu đến như là những bộ xử lý sự kiện.

Sử dụng nhiều delegate trong chương trình

Trong C#, một người dùng có thể gọi nhiều delegate trong cùng một chương trình. Để gọi một delegate ta thông qua tên của nó hoặc kiểu tham số truyền tới delegate. Ví dụ:

using System;
public delegate double CalculateArea(double val);
public delegate double CalculateVolume(double val);
class Cube
{
  static double Area(double val)
  {
    return 6 * (val * val);
  }
  static double Volume(double val)
  {
    return (val * val);
  }
  static void Main(string[] args)
  {
    CalculateArea objCalculateArea = new CalculateArea(Area);
    CalculateVolume objCalculateVolume = new CalculateVolume(Volume);
    Console.WriteLine("Surface Area of Cube: " + objCalculateArea(200.32));
    Console.WriteLine("Volume of Cube: " + objCalculateVolume(20.56));
  }
}

Trong đoạn mã trên, khi tạo các thể hiện của các delegate CalculateArea và CalculateVolume trong hàm Main() thì các tham chiếu của các phương thức Area() và Volume() được truyền như là các tham số tới các delegate tương ứng và các giá trị được truyền tới các thể hiện tương ứng của các delegate.

Multicast Delegate

Một delegate có thể đóng gói các tham chiếu của nhiều phương thức tại cùng một thời điểm. Hay nói cách khác một delegate có thể mang cùng lúc nhiều tham chiếu phương thức. Delegate như vậy gọi là 'Multicast Delegate'. Các phương thức mà multicast delegate tham chiếu (danh sách gọi) sẽ tự động được gọi khi delegate được gọi.

Multicast delegates trong C# được coi là kiểu con của lớp System.MulticastDelegate. Multicast delegate được định nghĩa tượng tự như delegate thông thường, tuy nhiên, kiểu trả về của multicast delegate phải là void. Đó là bởi vì nếu nó trả về một giá trị thì giá trị trả về chính là của phương thức cuối cùng trong danh sách các tham chiếu phương thức, và kiểu của giá trị trả về đó cũng chính là kiểu trả về của delegate; đây là điều không thích hợp. Cho nên delegate luôn luôn có kiểu trả về là void.

Để thêm phương thức vào danh sách gọi của multicast delegate ta cần sử dụng phép '+' hoặc phép '+='. Và ngược lại, để bỏ phương thức khỏi danh sách gọi ta sử dụng phép toán '-' hoặc '-='. Khi một multicast delegate được gọi thì tất cả các phương thức trong danh sách gọi sẽ được gọi tuần tự theo thứ tự phương thức nào được thêm vào multicast delegate trước sẽ được gọi trước. Ví dụ:

using System;
public delegate void Maths(int valOne, int valTwo);
class MathsDemo
{
  static void Addition(int valOne, int valTwo)
  {
    int result = valOne + valTwo;
    Console.WriteLine("Addition: " + valOne + " + " + valTwo + "= " + result);
  }
  static void Subtraction(int valOne, int valTwo)
  {
    int result = valOne - valTwo;
    Console.WriteLine("Subtraction: " + valOne + " - " + valTwo + "= " + result);
  }
  static void Multiplication(int valOne, int valTwo)
  {
    int result = valOne * valTwo;
    Console.WriteLine("Multiplication: " + valOne + " * " + valTwo + "= " +
    result);
  }
  static void Division(int valOne, int valTwo)
  {
    int result = valOne / valTwo;
    Console.WriteLine("Division: " + valOne + " / " + valTwo + "= " + result);
  }
  static void Main(string[] args)
  {
    Maths objMaths = new Maths(Addition);
    objMaths += new Maths(Subtraction);
    objMaths += new Maths(Multiplication);
    objMaths += new Maths(Division);
    if (objMaths != null)
    {
      objMaths(20, 10);
    }
  }
}

Trong ví dụ trên, hàm Main() tạo một thể hiện của delegate Maths tên là objMaths, và sau đó các toán tử '+=' được sử dụng để thêm các phương thức vào danh sách gọi của thể hiện objMaths; như vậy có nghĩa Maths là một multicast delegate.

Event

Trong C#, Event (Sự kiện) cho phép một đối tượng (nguồn của sự kiện) đưa ra thông báo cho những đối tượng khác - những đối tượng đăng ký sự kiện - về sự kiện (xảy ra một sự thay đổi nào đó).

Đặc điểm của Event

Mỗi sự kiện là một hành động được tạo ra bởi người dùng hoặc hệ thống và hành động này cho phép những đối tượng được yêu cầu sẽ thông báo tới những đối tượng hay những lớp khác việc xử lý sự kiện. Các sự kiện trong C# có những đặc điểm sau:

- Có thể được khai báo trong các lớp hoặc interface.

- Có thể được khai báo là abstract hoặc sealed.

- Có thể được khai báo là virtual.

- Được thực thi thông qua việc sử dụng delegate.

Sự kiện có thể được sử dụng để thực hiện các hành động tuỳ chỉnh mà không được sự hỗ trợ của C#. Sự kiện cũng hay được sử dụng để tạo các ứng dụng giao diện người dùng đồ họa (GUI), trong đó các sự kiện có thể xảy ra như chọn một mục trong danh sách (list), hoặc đóng cửa sổ theo dõi.

Tạo và sử dụng Event

Có bốn bước để thực thi các sự kiện trong C#, bao gồm:

  1. Định nghĩa một public delegate cho sự kiện
  2. Tạo sự kiện thông qua delegate
  3. Đăng ký để lắng nghe và xử lý sự kiện
  4. Kích hoạt sự kiện.

Các sự kiện sẽ sử dụng delegate để gọi các phương thức của các đối tượng đã đăng ký sự kiện. Khi một sự kiện nào đó (chứa các đăng ký) được kích hoạt thì các delegate liên quan sẽ được gọi tới.

Khai báo sự kiện

Để khai báo một sự kiện ta cần hai bước gồm khai báo delegate rồi sau đó mới khai báo sự kiện. Delegate sẽ truyền các tham số của phương thức thích hợp tới nơi gọi khi sự kiện được tạo, phương thức này gọi là bộ xử lý sự kiện. Sự kiện được khai báo bằng cách sử dụng từ khoá event và ngay sau là tên của delegate, và sau khi khai báo xong thì sự kiện sẽ được liên kết với delegate.

Cú pháp:

Khai báo delegate:
Bổ_từ_truy_cập delegate Kiểu_trả_về Tên_delegate(Danh_sách_tham_số);
Khai báo sự kiện:
Bổ_từ_truy_cập event Tên_delegate Tên_sự_kiện;

Ví dụ:

using System;
public delegate void PrintDetails(); //khai báo delegate trước
class TestEvent
{
  event PrintDetails Print; //rồi khai báo event
}

Đăng ký và huỷ đăng ký sự kiện

Đối tượng chỉ có thể đăng ký sự kiện khi sự kiện tồn tại, và để đăng ký sự kiện thì đối tượng sẽ thêm một delegate để gọi phương thức khi sự kiện được kích hoạt. Điều này được thực hiện bằng cách liên kết bộ xử lý sự kiện với sự kiện đã được tạo, liên kết được thực hiện bằng toán tử '+='. Thao tác như vậy gọi là đăng ký sự kiện.

Để huỷ bỏ đăng ký một sự kiện nào đó, ta sử dụng toán tử '-='.

Cú pháp:

Đăng ký sự kiện:

Tên_đối_tượng.Tên_sự_kiện += new Tên_delegate(Tên_phương_thức);

Huỷ đăng ký sự kiện:

Tên_đối_tượng.Tên_sự_kiện -= new Tên_delegate(Tên_phương_thức);

Ví dụ:

using System;
public delegate void PrintDetails();
class TestEvent
{
  event PrintDetails Print;
  void Show()
  {
    Console.WriteLine("Show me to the Console");
  }
  static void Main(string[] args)
  {
    TestEvent objTestEvent = new TestEvent();
    objTestEvent.Print += new PrintDetails(objTestEvent.Show); //câu lệnh đăng ký sự kiện
  }
}

Kích hoạt sự kiện

Khi một sự kiện được kích hoạt nó sẽ thông báo tới tất cả các đối tượng đã đăng ký nó; việc kích hoạt một sự kiện có thể là tác nhân người dùng hoặc hệ thống. Sau khi sự kiện đã được kích hoạt thì tất cả các bộ xử lý sự kiện liên quan sẽ được thực thi, khi đó delegate sẽ gọi tất cả các bộ xử lý đã được thêm vào sự kiện. Bạn hãy đảm bảo rằng trước khi sự kiện được kích hoạt thì nó phải được liên kết với các bộ xử lý sự kiện thích hợp, nếu không sự kiện sẽ được coi là null.

Cú pháp:

Tên_đối_tượng.Tên_sự_kiện(Danh_sách_tham_số);

Ví dụ:

using System;
public delegate void PrintDetails();
class TestEvent
{
  event PrintDetails Print;
  void Show()
  {
    Console.WriteLine("Hay hien thi toi ra man hinh.");
  }
  static void Main(string[] args)
  {
    TestEvent objTestEvent = new TestEvent();
    objTestEvent.Print += new PrintDetails(objTestEvent.Show);
    objTestEvent.Print(); //câu lệnh kích hoạt sự kiện
  }
}

Sự kiện và thừa kế

Một sự kiện trong C# chỉ có thể được gọi trong lớp chứa sự kiện đó, như vậy có nghĩa lớp dẫn xuất sẽ không thể gọi trực tiếp sự kiện. Tuy nhiên, sự kiện lại có thể được gọi gián tiếp bằng cách tạo một phương thức có bổ từ truy cập không phải là private trong lớp cơ sở. Ví dụ:

using System;
public delegate void Display(string msg);
public class Parent
{
  event Display Print;
  protected void InvokeMethod() //phương thức nơi đăng ký sự kiện
  {
    Print += new Display(PrintMessage);
    Check();
  }
  void Check()
  {
    if (Print != null)
    {
      PrintMessage("Su kien co the duoc goi truc tiep bang cach tao mot phuong thuc protected.");
    }
  }
  void PrintMessage(string msg)
  {
    Console.WriteLine(msg);
  }
}
class Child : Parent
{
  static void Main(string[] args)
  {
    Child objChild = new Child();
    objChild.InvokeMethod();
  }
}

Lưu ý

- Mỗi sự kiện chỉ có thể được khai báo là abstract ở trong lớp abstract, và vì vậy sự kiện phải được override trong lớp dẫn xuất của lớp abstract. Các sự kiện abstract được dùng với mục đích có thể được tuỳ chỉnh trong các lớp dẫn xuất.

- Một sự kiện được khai báo là sealed trong lớp cơ sở để ngăn chặn việc lớp dẫn xuất gọi nó. Sự kiện sealed không thể được override trong bất kỳ lớp dẫn xuất nào của lớp cơ sở; điều này sẽ đảm bảo sự an toàn cho sự kiện khi nó hoạt động.

Collection

Tổng quan

Collection là một tập hợp các dữ liệu có liên quan đến nhau nhưng không nhất thiết phải cùng kiểu. Nó có thể được thiết lập hoặc chỉnh sửa một cách động ngay trong quá trình chương trình đang được thực thi. Việc truy cập collection nói chung khá tương tự so với việc truy cập mảng, trong đó các phần tử cũng có thể được truy cập thông qua chỉ số. Tuy vậy, cũng có một số điểm khác biệt giữa mảng và collection trong C# được thể hiện ở bảng dưới đây:

Mảng (Array) Collection
Không thể thay đổi kích thước khi chương trình còn đang thực thi (run-time) Có thể thay đổi được kích thước tại thời điểm run-time
Các phần tử mảng có cùng kiểu dữ liệu Các phần tử có thể khác kiểu dữ liệu
Không có sẵn các phương thức để thao tác trên các phần tử mảng. Có sẵn các phương thức để thao tác trên các phần tử.

Namespace System.Collections

Đây là một namespace cho phép bạn cấu trúc và thao tác với một tập hợp các đối tượng, tập hợp này có thể bao gồm các phần tử có kiểu khác nhau. Nó cũng định nghĩa một số lượng tương đối các loại tập hợp khác nhau để người dùng lựa chọn, ví dụ như các loại mảng động, list, và dictionary.

System.Collections cũng định nghĩa các lớp và giao diện (interface) và trong chúng lại định nghĩa những tập hợp riêng, phổ biến và hay dùng nhất phải kể đến:

- Lớp ArrayList: Cung cấp một tập hợp tương tự như mảng ngoại trừ những điểm khác biệt là nó có thêm phần tử và truy xuất phần tử một cách động và nó cũng có thể chứa được tất cả các loại dữ liệu khác nhau (bao gồm cả đối tượng) cho dù những đối tượng đó có cùng kiểu hay không.

- Lớp Stack: Tập hợp này tuân theo nguyên tắc lưu trữ Last-In-First-Out (LIFO), tức là phần tử cuối cùng được chèn vào thì sẽ bị xóa đầu tiên và ngược lại.

- Lớp Hashtable: Cung cấp một tập hợp gồm các cặp khóa/giá trị (key/value) và được sắp xếp dựa trên mã băm của các khóa.

- Lớp SortedList: Cung cấp một tập hợp lưu trữ theo các cặp key/value đã được sắp xếp trước khi lưu dựa trên các key.

- IDictionary: Dùng để hiển thị tập dữ liệu theo các cặp key/value.

- IDictionaryEnumerator: Dùng để liệt kê các phần tử kiểu từ điển (dictionary).

- IEnumerable: Định nghĩa một bộ enumerator dùng để liệt kê dữ liệu trong tập hợp.

- ICollection: Chỉ định kích thước và đồng bộ hóa các phương thức cho tất cả các tập dữ liệu.

- IEnumerator: Hỗ trợ việc liệt kê các phần tử của tập dữ liệu.

- IList: Đại diện cho tập hợp các thành phần mà có thể được truy cập thông qua số chỉ mục của chúng.

Đoạn mã dưới đây minh họa việc sử dụng các lớp và giao diện thông dụng của namespace System.Collections.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
class NhanVien : DictionaryBase {
  public void Them(int id, string ten) {
    Dictionary.Them(id, ten);
  }
  public void OnRemove(int id) {
    Console.WriteLine("Ban se xoa ban ghi co ID: " + id);
    Dictionary.Remove(id);
  }
  public void ChiTiet() {
    IDictionaryEnumerator objIDE = Dictionary.GetEnumerator();
    while (objEnumerate.MoveNext()) {
      Console.WriteLine(objEnumerate.Key.ToString() + "\t" + objIDE.Value);
    }
  }
  static void Main(string[] args) {
    NhanVien objNV = new NhanVien();
    objEmployee.Them(102, "Hung");
    objEmployee.Them(105, "Minh");
    objEmployee.Them(106, "Quan");
    Console.WriteLine("Ban dau trong Dictionary co nhung ban ghi sau:");
    objNV.ChiTiet();
    objNV.OnRemove(106);
    Console.WriteLine("Sau khi xoa, Dictionary se chua cac ban ghi sau:");
    objNV.ChiTiet();
  }
}

Phân tích đoạn mã: Lớp NhanVien được thừa kế từ lớp DictionaryBase, đây là một lớp trừu tượng. Thông tin chi tiết của mỗi nhân viên được chèn vào Dictionary thông qua phương thức Them() của nó. Ở đây ta xây dựng phương thức Them() trong lớp NhanVien gồm hai tham số là idten và hai tham số này sẽ được truyền tới phương thức Dicitonary.Add(), lớp Dictionary sẽ lưu trữ các giá trị này theo cặp key/value. Phương thức OnRemove() của DictionaryBase được ghi đè, phương thức này có một đối số để xác định key của phần tử rồi từ đó xóa cặp key/value tương ứng khỏi Dictionary thông qua phương thức Remove() của nó. Phương thức GetEnumerator() của Dictionary sẽ trả về một IDictionaryEnumerator.

Namespace System.Collections.Generic

Xét một mẫu đơn trực tuyến được sử dụng bởi các sinh viên đăng ký một bài thi của một trường đại học. Mẫu đơn có thể được sử dụng để áp dụng cho bài thi của bất kỳ khóa học nào được cung cấp bởi các trường đại học. Tương tự như vậy, trong C#, generic cho phép bạn định nghĩa các cấu trúc dữ liệu gồm các chức năng mà có thể được thực thi cho bất kỳ kiểu dữ liệu nào. Vì vậy, generic cho phép bạn tái sử dụng mã lệnh đối với các kiểu dữ liệu khác nhau mà không phải xây dựng lại mã lệnh nữa.

Để tạo generic, bạn nên sử dụng các lớp có sẵn của namespace System.Collections.Generic. Những lớp này đảm bảo sự an toàn kiểu và đây là tính năng của C# để đảm bảo một giá trị được đối xử như kiểu mà nó được khai báo.

Lưu ý: Loại namespace này tương tự như System.Collections vì cả hai đều cho phép bạn tạo các collection. Tuy nhiên, collection kiểu generic có tính năng an toàn kiểu.

System.Collections.Generic bao gồm các lớp, interface và cấu trúc cho phép định nghĩa các tập hợp chung khác nhau. Dưới đây sẽ trình bày cụ thể hơn về các thành phần này của namespace.

Lớp

Namespace System.Collections.Generic bao gồm các lớp cho phép bạn tạo các tập hợp an toàn kiểu. Bảng dưới đây liệt kê các lớp thường xuyên được sử dụng trong namespace này.

Lớp Mô tả
List<T> Cung cấp một tập hợp generic gồm các mục có thể thay đổi kích thước động
Stack<T> Cho phép thực hiện nguyên lý LIFO (Last In First Out), có nghĩa là mục nào được chèn vào sau cùng thì sẽ bị xóa trước tiên
Queue<T> Cho phép thực hiện nguyên lý FIFO, mục nào được chèn vào trước tiên thì sẽ bị xóa trước tiên
Dictionary<K, V> Cung cấp một tập hợp generic kiểu từ điển gồm các cặp khóa/giá trị
SortedDictionary<K,V> Tập hợp generic cho phép sắp xếp các cặp khóa/giá trị của các mục được sắp xếp dựa trên khóa của chúng
LinkedList<T> Thực hiện các danh sách nhân đôi được liên kết bằng cách lưu trữ các phần tử trong nó

Interface và Cấu trúc

System.Collections.Generic cũng bao gồm các interface và cấu trúc mà có thể được thực thi để tạo các tập hợp an toàn kiểu. Dưới đây là một số interface và cấu trúc phổ biến:

- ICollection: Định nghĩa các phương thức để điều khiển các tập hợp generic khác nhau.

- IEnumerable: Là một interface định nghĩa một bộ liệt kê để tiến hành liệt kê một tập hợp có kiểu xác định.

- IComparer: Định nghĩa một phương thức để so sánh hai đối tượng.

- IDictionary: Biểu diễn một tập hợp chung gồm các cặp khóa và giá trị.

- IEnumerator: Hỗ trợ việc liệt kê đơn giản thông qua các phần tử của tập hợp chung.

- IList: Biểu diễn một tập hợp gồm các mục mà có thể được truy cập bằng cách sử dụng chỉ mục.

- Cấu trúc Dictionary.Enumerator: Liệt kê các phần tử của Dictionary.

- Cấu trúc Dictionary.KeyCollection.Enumerator: Liệt kê các phần tử của một Dictionary.KeyCollection.

- Cấu trúc Dictionary.ValueCollection.Enumerator: Liệt kê các phần tử của một Dictionary.ValueCollection.

- Cấu trúc Key/ValuePair: Định nghĩa một cặp khóa/giá trị.

Đoạn mã sau biểu diễn cách sử dụng các lớp, interface và cấu trúc phổ biến của namespace System.Collection.Generic.

using System;
using System.Collections;
using System.Collections.Generic;
class Student : IEnumerable {
  LinkedList<string> objList = new LinkedList<string>();
  public void ChiTietSinhVien() {
    objList.AddFirst("An");
    objList.AddFirst("Minh");
    objList.AddFirst("Duong");
    objList.AddFirst("Long");
    objList.AddFirst("Anh");
    Console.WriteLine("So luong phan tu co trong danh sach: " + objList.Count);
  }
  public void HienThi(string ten) {
    LinkedListNode<string> objLLN;
    int dem = 0;
    for (objLLN = objList.First; objLLN != null; objLLN = objLLN.Next) {
      if (objLLN.Value.Equals(name)) {
        count++;
      }
    }
    Console.WriteLine("Ten " + ten + " xuat hien " + dem + " lan trong danh sach");
  }
  public IEnumerator GetEnumerator() {
    return objList.GetEnumerator();
  }
  static void Main(string[] args) {
    Student objStu = new Student();
    objStu.ChiTietSinhVien();
    foreach (string str in objStu) {
      Console.WriteLine(str);
    }
    objStudent.HienThi("Anh");
  }
}

Phân tích đoạn mã: Trong đoạn mã trên, lớp Student thực thi giao diện IEnumerable. Một danh sách liên kết đôi kiểu chuỗi được tạo. Phương thức ChiTietSinhVien() được định nghĩa để chèn các giá trị vào danh sách liên kết. Phương thức AddFirst() của lớp LinkedList được dùng để chèn các giá trị vào danh sách liên kết. Phương thức HienThi() có một đối số kiểu chuỗi được dùng để tìm giá trị trong danh sách. Lớp LinkedListNode có tham chiếu kiểu chuỗi được tạo trong phương thức HienThi(). Tham chiếu này được dùng để duyệt danh sách liên kết. Mỗi khi tìm thấy sự tương thích giữa giá trị của đối số phương thức HienThi() với giá trị trong danh sách thì biến dem lại tăng giá trị lên 1. Biến này dùng để đếm số lần xuất hiện của chuỗi muốn tìm. Khi phương thức GetEnumerator() được thực thi thì nó sẽ trả về một IEnumerator. IEnumerator được dùng để duyệt danh sách và hiển thị tất cả các giá trị đã lưu trong danh sách liên kết.

ArrayList

Tổng quan

Lớp ArrayList là lớp có kích thước có thể thay đổi tự động theo hướng tăng hoặc hoặc giảm. Không giống như lớp Array, ArrayList có thể lưu trữ các phần tử có kiểu khác nhau.

ArrayList cho phép ta xác định được kích thước của tập hợp ngay trong quá trình thực thi chương trình bằng cách định nghĩa dung lượng (thông qua thuộc tính Capacity) để từ đó xác định số lượng phần tử của danh sách mảng mà nó có thể chứa. Dung lượng mặc định của ArrayList là 16 (mảng có thể chứa 16 phần tử, tức là Capacity = 16); trong trường hợp số lượng phần tử bằng với dung lượng thì dung lượng sẽ tự động được tăng gấp đôi.

ArrayList cho phép ta có thể thêm (thậm chí cả giá trị null), sửa, xóa các phần tử mảng ngay trong khi chương trình còn đang thực thi (run-time). Các phần tử của ArrayList được truy cập thông qua vị trí chỉ số.

Với ArrayList bạn cũng không cần phải quan tâm đến việc quản lý bộ nhớ nữa.

Các phần tử của ArrayList sẽ được đánh chỉ số bắt đầu từ 0.

Cú pháp tạo ArrayList

Để tạo một thể hiện của lớp ArrayList thì điều đầu tiên là bạn cần phải khai báo namespace chứa nó: using System.Collections;.

Sau đó ta mới sử dụng được ArrayList. Cú pháp cụ thể cho việc như sau:

ArrayList Tên_arraylist = new ArrayList();

Đoạn mã sau tạo một thể hiện của ArrayList có tên là arr:

ArrayList arr = new ArrayList();

Các phương thức của lớp ArrayList

ArrayList có khá nhiều phương thức được xây dựng sẵn, điều này sẽ giúp giảm rất nhiều công sức cho người dùng cũng như giảm được thời gian phát triển ứng dụng. Dưới đây là các phương thức phổ biến của ArrayList.

Add()

Phương thức này dùng để thêm một phần tử vào cuối mảng. Cú pháp của phương thức như sau:

Tên_arraylist.Add(phần_tử);

Đoạn mã sau sẽ thêm một số phần tử với các kiểu khác nhau (bao gồm cả giá trị null) vào mảng arr:

using System;
using System.Collections;
class Student
{
  public string fullname;
}
class Demo
{
  static void Main(string[] args)
  {
    ArrayList arr = new ArrayList();
    arr.Add(123); //thêm số 123 vào mảng
    arr.Add("123"); //thêm chuỗi "123" vào mảng
    arr.Add(true); //thêm giá trị true (kiểu bool) vào mảng
    arr.Add(null); //thêm giá trị null vào mảng
    Student st = new Student(); //tạo đối tượng st của lớp Student
    st.fullname = "ABCD";
    arr.Add(st); //rồi thêm vào mảng
    arr.Add(null); //thêm một giá trị null nữa vào mảng
  }
}

Remove()

Phương thức này dùng để xóa phần tử khỏi mảng. Nếu trong trường hợp có hai hay nhiều phần tử giống nhau thì phương thức sẽ xóa phần tử có chỉ số nhỏ nhất khỏi mảng. Ví dụ:

arr.Remove(null); //xóa phần tử null
arr.Remove(st); //xóa phần tử st
arr.Remove("123"); //xóa phần tử "123"

RemoAt()

Phương thức này sẽ xóa phần tử có chỉ số được chỉ ra tương ứng. Đoạn mã sau xóa phần tử có chỉ số 3 khỏi mảng arr:

arr.RemoveAt(3);

Insert()

Phương thức này dùng để thêm mới một phần tử vào vị trí có chỉ số được chỉ ra. Cú pháp của phương thức như sau:

Tên_arraylist.Insert( chỉ_số, phần_tử );

Đoạn mã sau thêm phần tử có giá trị là một chuỗi "XYZ" vào mảng arr tại ví trị có chỉ số là 3:

arr.Insert(3,"XYZ");

Contains()

Phương thức này dùng để kiểm tra một phần tử nào đó có nằm trong danh sách mảng hay không. Nếu tìm thấy sẽ trả về true, ngược lại thì trả về false.

Đoạn mã sau sẽ kiểm tra xem trong mảng arr có chứa phần tử có giá trị là null hay không.

if (arr.Contains(null))
{
  Console.WriteLine("Mang co chua gia tri null");
}
else
{
  Console.WriteLine("Mang khong chua null");
}

IndexOf()

Phương thức này dùng để lấy chỉ số của phần tử được chỉ ra tương ứng. Trong trường hợp có nhiều phần tử giống nhau thì phương thức sẽ lấy chỉ số nhỏ nhất; trường hợp không có phần tử trong danh sách mảng thì phương thức sẽ trả về giá trị -1. Cú pháp sử dụng của phương thức này như sau:

Tên_arraylist.IndexOf(Đối_tượng);

Ví dụ sau sẽ in ra chỉ số của phần tử có giá trị "XYZ". Trường hợp giá trị in ra là -1 thì tức là phần tử này không tồn tại trong mảng arr:

Console.WriteLine(arr.IndexOf("XYZ")); //tìm thấy => trả về chỉ số tương ứng, ngược lại trả về -1

Reverse()

Phương thức này dùng để đảo ngược trật tự các phần tử của danh sách mảng. Ví dụ:

arr.Add(1);
arr.Add(2);
arr.Add(3);
arr.Add(4); //ban đầu trật tự của các phần tử là 1, 2, 3, 4
arr.Reverse(); //sau khi đảo ngược thì trật tự sẽ là 4, 3, 2, 1

Sort()

Phương thức này dùng để sắp xếp các phần tử mảng theo trật tự tăng dần.

Lưu ý: Bạn chỉ có thể sử dụng phương thức này nếu mảng chứa các phần tử có cùng kiểu và phải là kiểu giá trị chứ không được là kiểu tham chiếu (chẳng hạn bạn tạo hai đối tượng st1 và st2 của lớp Student rồi thêm vào mảng arr, sau đó bạn thực hiện câu lệnh arr.Sort() thì trình dịch sẽ báo lỗi runtime). Ví dụ:

arr.Add(2);
arr.Add(4);
arr.Add(1);
arr.Add(3); //ban đầu trật tự của các phần tử là 2, 4, 1, 3
arr.Sort()//sau khi sắp xếp thì trật tự sẽ là 1, 2, 3, 4

Clear()

Phương thức này dùng để xóa tất cả các phần tử khỏi mảng. Ví dụ:

arr.Clear(); //xóa tất cả các phần tử khỏi mảng arr

Các thuộc tính của ArrayList

Dưới đây là một số thuộc tính cơ bản của lớp ArrayList.

Capacity

Thuộc tính Capacity của ArrayList dùng để thiết lập hoặc lấy số lượng phần tử mà mảng ArrayList có thể chứa. Khi mảng chưa có phần tử nào thì Capacity=0, và giá trị của Capacity sẽ tự động thay đổi theo quy tắc: Capacity là số nhỏ nhất của n*4 (n=0, 1, 2, ...) nhưng phải lớn hơn hoặc bằng kích thước của mảng. Ví dụ, nếu mảng có kích thước là 1 thì Capacity=4, mảng có kích thước là 6 thì Capacity=8, mảng có kích thước là 12 thì Capacity=12, ...

Bạn cũng có thể chủ động thiết lập giá trị của Capacity theo ý muốn; giá trị gán cho Capacity phải lớn hơn hoặc bằng kích thước hiện thời của mảng.

Câu lệnh sau sẽ in ra màn hình Capacity của mảng arr:

Console.WriteLine(arr.Capacity);

Câu lệnh dưới đây sẽ gán giá trị 21 cho Capacity của mảng arr:

arr.Capacity = 21;

Count

Thuộc tính Count dùng để lấy kích thước thực tế của mảng ArrayList. Câu lệnh sau sẽ in ra màn hình kích thước thực tế của mảng arr:

Console.WriteLine("So luong phan tu thuc su ma mang arr dang chua la: " + arr.Count);

IsFixedSize

Thuộc tính này dùng để chỉ ra kích thước của mảng có được đặt cố định hay không. Nếu kích thước được đặt cố định thì giá trị của thuộc tính là true, ngược lại là false. Ví dụ:

if(arr.IsFixedSize)
  Console.WriteLine("Co dinh");
else
  Console.WriteLine("Khong co dinh");

IsReadOnly

Thuộc tính này dùng để chỉ ra rằng mảng có thiết lập chế độ read-only hay không, nếu có thì giá trị của thuộc tính là true, ngược lại là false. Ví dụ:

if (arr.IsReadOnly)
  
Console.WriteLine("Mang da thiet lap che do ReadOnly");
else
  Console.WriteLine("Mang khong thiet lap che do ReadOnly");

Practical 5

EXERCISE 1: READER MANAGEMENT SYSTEM

1. Design delegate ReadNewsDelegate that can references to methods as below:

   void TestMethod(string news)

2. Design class PostOffice:

   · Has an event NewsEvent that can use ReadNewsDelegate delegate

   · Has property ReaderList ( System.Collections.ArrayList)

   · Has method void UpdateNews(string news) that use to invoke the event NewsEvent

3. Design interface Ireader:

   + Properties:

      · Int ID

      · String Name

      · DateTime BirthDate

      · String Email

   + Methods:

      · void ReadNews(string news)

      · void Register(PostOffice p)

      · void Unregister(PostOffice p)

4. Design class Reader implements interface Ireader:

   · Method ReadNews prints Name, BirthDate, Email of reader, after that print content of news to the console.

   · Method Register adds reader to ReaderList of the PostOffice, and suscribers method ReadNews of reader to event NewsEvent of the PostOffice.

   · Method Unregister removes reader from ReaderList of the PostOffice, and removes method ReadNews of reader from event NewsEvent of the PostOffice.

5. Design a menu:

   1. Add new reader
   2. Remove a reader
   3. Publish new news
   4. Exi
t

· When users choose 1: create an instant of class Reader, ask users to input Name, BirthDate, Email, the ID of instant is auto increment (ID++). Call method Register of the user.

· When users choose 2: ask them to input a name, afterthat search the user whose Name equals the name was inputted. Call method Unregister of the user.

· When users choose 3: ask them to input content of the news. After that pass the news to method UpdateNews of the PostOffice ( there is only an instant of the PostOffice was created)


EXERCISE 2: BOOKS MANAGEMENT SYSTEM

In this exam, you’ll have to create a Books Management system. The system allows input, list, search … books.

1. Create an interface name IBook  contains these properties and methods:

a. Properties

   · ID (int)

   · Name (string)

   · PublishDate (DateTime)

   · Author (string)

   · Language (string)

   · AveragePrice (float) – Read only property

b. Methods

   · void Display()

2. Create a class name Book:

a. Implements the IBook interface in step 1

b. The method Display will print all Name, PublishDate, Author, Language and AveragePrice of the book to the console

c. Declare an array name PriceList type int has size of 5 elements 

d. Create an indexer uses the array PriceList in step 2c.

e. Create a method named Calculate to set AveragePrice = average of 5 int elements in PriceList array.

3. Display  a tasks menu to choose:

   1. Insert new book
   2. View list of books
   3. Average Price
   4. Exit

4. If user type 1 from keyboard then:

a. Create a new Book  instance and input Name, PublishDate, Author, Language and then ask user to enter 5 prices and set to the instance indexer

   - The ID is auto increament ( ID++ )

   - Be sure to check format of PublishDate

b. Create a Dictionary<int, Book> to keep the Book instance in step a with key = ID and value = the book instance

5. If user type 2 from keyboard then:

Loop from all book instances in the Dictionary<int, Book> then executes the Display method from IBook interface.

6. If user type 3 from keyboard then:

Loop from all book instances in the Dictionary<int, Book>  then executes the Calculate method and then execute the Display method.

7. While user not chooses Exit (type 4 from keyboard) then go back to the menu step 3 to ask user chooses an option.

 

Hashtable

Giới thiệu

Bạn đang ở một khác sạn, bạn có việc bận cần ra ngoài, bạn khóa cửa phòng và chìa khóa bạn gửi ở quầy lễ tân; những vị khách khác khi ra ngoài cũng gửi chìa ở quầy lễ tân. Ở đây mỗi chìa khóa sẽ là một giá trị nhận diện duy nhất cho một phòng của khách sạn, hay có nghĩa mỗi phòng của khách sạn sẽ được nhận diện chỉ bởi một chìa khóa.

hashtable-csharp

Lớp Hashtable (bảng băm) trong C# cũng có ý nghĩa tương tự như khách sạn, trong hashtable chứa các value (các phòng) và mỗi value sẽ có duy nhất một key (chìa khóa) nhận diện, và việc truy cập đến các value (giá trị) của bảng băm sẽ thông qua các key. Như vậy, khi chèn một phần tử vào hashtable thì nó sẽ bao gồm cặp key/value. Hashtable sử dụng kỹ thuật 'băm' để truy xuất các value tương ứng với key, theo đó thao tác băm sẽ tạo ra được mã băm cho key và mã băm đó sẽ được dùng để nhận diện value tương ứng của key.

Khi bạn tìm value thông qua key thì mã băm sẽ được sử dụng để định vị bản ghi mong muốn, ví dụ như tên của sinh viên có thể được sử dụng như một key để truy xuất tới id và địa chỉ nơi cư trú tương ứng của sinh viên đó.

Lớp Hashtable cung cấp các phương thức cũng như các thuộc tính dùng để thêm cũng như thao tác với dữ liệu trong bảng băm. Dưới đây sẽ trình bày chi tiết các phương thức của như thuộc tính của lớp Hashtable.

Lớp Hashtable thuộc namespace: System.Collections. Để tạo một bảng băm có tên objTable, ta làm như sau:

Hashtable objTable = new Hashtable();

Các phương thức của lớp Hashtable

1. Add()

Phương thức này dùng để thêm phần tử vào bảng băm, phần tử thêm vào sẽ có hai giá trị theo cặp key/value, trong đó key phải là duy nhất (không trùng với những key đã có trước đó), nếu không trình dịch sẽ báo lỗi runtime.

Cú pháp:

hashtable_name.Add(key, value);

Ví dụ:

objTable.Add(001, "John");
objTable.Add(002, "Peter");
objTable.Add(003, "James");
objTable.Add(004, "Joe");

2. Remove()

Phương thức này dùng để xóa phần tử có key được chỉ ra tương ứng với đối số của phương thức khỏi bảng băm.

Cú pháp:

hashtable_name.Remove(key);

Ví dụ:

objTable.Remove(004);

3. ContainsKey()

Phương thức này dùng để kiểm tra xem trong bảng băm có key được chỉ ra ở phần đối số của phương thức hay không. Ví dụ:

if (objTable.ContainsKey(004))
  Console.WriteLine("Yes");
else
  Console.WriteLine("No");

4. ContainsValue()

Phương thức này dùng để kiểm tra xem trong bảng băm có key được chỉ ra ở phần đối số của phương thức hay không. Ví dụ:

if (objTable.ContainsValue("Joe"))
  Console.WriteLine("Yes");
else
  Console.WriteLine("No");

5. Clear()

Phương thức này dùng để xóa tất cả các phần tử khỏi bảng băm. Ví dụ:

if (objTable.Count > 0)
  objTable.Clear();
else
  Console.WriteLine("Empty");

Các thuộc tính của Hashtable

1. Count

Thuộc tính này dùng để đếm số phần tử thực tế của bảng băm. Ví dụ:

if (objTable.Count == 24)
  Console.WriteLine("Full");

2. Keys

Thuộc tính này cung cấp một ICollection chứa danh sách các key của bảng băm. Ví dụ:

foreach (object key in objTable.Keys)
  Console.WriteLine(key);

3. Values

Thuộc tính này cung cấp một ICollection chứa danh sách các value của bảng băm. Ví dụ:

foreach (object value in objTable.Values)
  
Console.WriteLine(value);

4. IsReadOnly

Thuộc tính này dùng để kiểm tra xem Hashtable có phải là read-only hay không, nếu phải thì trả về true, ngược lại trả vể false. Ví dụ:

if (objTable.IsReadOnly)
  Console.WriteLine("Yes");
else
  Console.WriteLine("No");

Ví dụ về Hashtable

Ví dụ 1:

using System;
using System.Collections;
class HashCollection
{
  static void Main(string[] args)
  {
    Hashtable objTable = new Hashtable();
    objTable.Add(001, "John");
    objTable.Add(002, "Peter");
    objTable.Add(003, "James");
    objTable.Add(004, "Joe");
    Console.WriteLine("Number of elements in the hash table: " + objTable.Count);
    ICollection objCollection = objTable.Keys;
    Console.WriteLine("Original values stored in hashtable are: ");
    foreach (int i in objCollection)
    {
      Console.WriteLine(i + " : " + objTable[i]);
    }
    if (objTable.ContainsKey(002))
    {
      objTable[002] = "Patrick";
    }
    Console.WriteLine("Values stored in the hashtable after removing values");
    foreach (int i in objCollection)
    {
      Console.WriteLine(i + " : " + objTable[i]);
    }
  }
}

Trong ví dụ trên, phương thức Add() chèn các phần tử theo cặp key/value tương ứng vào bảng băm objTable; thuộc tính Count đếm số phần tử thực tế của bảng băm; thuộc tính key cung cấp một danh sách các key tới interface ICollection;  phương thức ContainsKey() sẽ kiểm tra xem  bảng băm có chứa key được chỉ ra trong đối số của phương thức hay không, nếu bảng băm có chứa key 002 thì thuộc tính mặc định Item sẽ được gọi tới thông qua cặp ngoặc vuông [] và sẽ thay thế value Peter thành Patrick. Việc hiển thị các value hay các key có thể theo thứ tự tăng hoặc giảm, hoặc cũng có thể là ngẫu nhiên tùy thuộc vào mã băm của từng key. Kết quả của ví dụ như sau:

hashtable-csharp-example1

Ví dụ 2:

using System;
using System.Collections;
class Authors
{
  static void Main(string[] args)
  {
    Hashtable objAuthors = new Hashtable();
    objAuthors.Add("AU01", "John");
    objAuthors.Add("AU04", "Mary");
    objAuthors.Add("AU09", "William");
    objAuthors.Add("AU11", "Rodrick");
    Console.WriteLine("Read-only : " +
    objAuthors.IsReadOnly);
    Console.WriteLine("Count : " + objAuthors.Count);
    IDictionaryEnumerator objCollection =
    objAuthors.GetEnumerator();
    Console.WriteLine("List of authors:\n");
    Console.WriteLine("Author ID \t Name");
    while (objCollection.MoveNext())
    {
      Console.WriteLine(objCollection.Key + "\t\t " +
      objCollection.Value);
    }
    if (objAuthors.Contains("AU01"))
    {
      Console.WriteLine("\nList contains author with id AU01");
    }
    else
    {
      Console.WriteLine("\nList does not contain author with id AU01");
    }
  }
}

Trong ví dụ trên, phương thức Add() thêm các phần tử vào objAuthors; phương thức IsReadOnly() kiểm tra xem các giá trị trong bảng băm có thể thay đổi được hay không. Phương thức Contains() sẽ kiểm tra xem giá trị 'AU01' có tồn tại trong danh sách các giá trị trị hay không. phương thức GetEnumerator() sẽ trả về một IDictionaryEnumerator và gán cho objCollection, sau đó dùng vòng lặp while để hiển thị các khóa và giá trị của các phần tử ra màn hình.

hashtable-csharp-example2

SortedList

Tổng quan

Lớp SortedList dùng để chứa tập hợp các cặp key/value (khóa/giá trị) đã được sắp xếp theo trật tự tăng dần của các key, và các key cũng sẽ được đánh chỉ số bắt đầu từ 0 đối với các key đã được sắp xếp.

Mặc định lớp SortedList sẽ sắp xếp các phần tử theo thứ tự tăng dần (của các key), tuy nhiên, điều này có thể thay đổi nếu một đối tượng IComparrable được truyền tới hàm tạo của lớp SortedList. Những phần tử khi đó hoặc là được truy cập bằng cách sử dụng các key tương ứng hoặc là thông qua các chỉ số của các phần tử.

Nếu ta truy cập các phần tử thông qua các key thì lớp SortedList lại hoạt động giống như một lớp Hashtable, còn nếu truy cập thông qua chỉ số thì lớp này lại đóng vai trò như một mảng.

SortedList cho phép lưu trữ mọi loại dữ liệu, trong đó key có thể có kiểu giá trị bất kỳ, value có kiểu bất kỳ. Tuy nhiên, trong một sortedlist ta nên đặt các key có cùng một kiểu.

Lớp SortedList có các phương thức và thuộc tính được dùng để thêm và thao tác với dữ liệu trong một danh sách đã được sắp xếp.

Lớp SortedList thuộc namespace: System.Collections.

Để tạo một đối tượng có tên objSortList, ta làm như sau:

SortedList objSortList = new SortedList();

Các phương thức của lớp SortedList

1. Add()

Phương thức này dùng để thêm một phần tử với cặp key/value cụ thể vào mảng. Lưu ý rằng các phần tử thêm vào mảng nên có key cùng kiểu (ví dụ như tất cả là kiểu int, hoặc string, ...). Cú pháp:

Name_sortedlist.Add(key, value);

Ví dụ sau sẽ thêm một số phần tử vào objSortList:

using System;
using System.Collections;
class SortedCollection
{
  static void Main(string[] args)
  {
    SortedList objSortList = new SortedList();
    objSortList.Add("Thang", "Quan tri"); //index = 2
    objSortList.Add("Binh", "Nhan su"); //index = 0
    objSortList.Add("Thanh", "Tai chinh"); //index = 3
    objSortList.Add("Huyen", "Marketing"); //index = 1
  }
}

2. Remove()

Phương thức này dùng để xóa phần tử có key tương ứng khỏi mảng. Lưu ý rằng key ứng với phần tử đem xóa có thể không có trong danh sách key của mảng, nhưng nhất thiết phải cùng kiểu với các danh sách key của mảng (ví dụ nếu các key của mảng có kiểu string thì key của phần tử đem xóa cũng phải có kiểu string). Cú pháp của phương thức này như sau:

Name_sortedlist.Remove(key);

Ví dụ sẽ xóa phần tử có key là "Joel":

objSortList.Remove("Joel");

3. GetKey()

Phương thức này dùng để lấy key tương ứng với chỉ số (index) được chỉ ra. Các chỉ số ở đây là các số tự nhiên (0, 1, 2, ...). Ví dụ:

Console.WriteLine("Key = " + objSortList.GetKey(3)); //sẽ in ra: Key = Thanh

4. GetByIndex()

Phương thức này dùng để lấy value ứng với index được chỉ ra. Ví dụ:

Console.WriteLine("Value = " + objSortList.GetByIndex(3)); //sẽ in ra: Value = Tai chinh

5. ContainsKey()

Phương thức này dùng để kiểm tra xem một key nào đó có nằm trong danh sách các key của mảng hay không, nếu có thì trả về true, ngược lại trả về false. Ví dụ:

if (objSortList.ContainsKey("Quan"))
{
  Console.WriteLine("Yes");
}
else
{
  Console.WriteLine("No");
}

6. ContainsValue()

Phương thức này dùng để kiểm tra xem một value nào đó có nằm trong danh sách các value của mảng hay không, nếu có thì trả về true, ngược lại trả về false. Ví dụ:

if (objSortList.ContainsValue("Finance"))
{
  Console.WriteLine("Yes");
}
else
{
  Console.WriteLine("No");
}

7. RemoveAt()

Phương thức này dùng để xóa phần tử có chỉ số (index) được chỉ ra tương ứng. Nếu chỉ số nằm ngoài dải giá trị chỉ số thì trình dịch sẽ báo lỗi. Ví dụ:

objSortList.RemoveAt(3); //xóa phần tử có chỉ số = 3

Các thuộc tính của SortedList

1. Capacity

Thiết lập hoặc lấy số lượng phần tử mà mảng SortedList có thể chứa. Ý nghĩa của thuộc tính này cũng khá tương tự với thuộc tính Capacity của ArrayList, có điểm khác là kích thước của Capacity của SortedList tự động thay đổi theo bội số của 16 (n*16, n=0, 1, 2, ...).

Ví dụ sau sẽ in ra giá trị trong thuộc tính Capacity:

Console.WriteLine( objSortList.Capacity );

Còn ví dụ sau sẽ gán giá trị 25 cho Capacity:

objSortList.Capacity = 25;

2. Count

 Thuộc tính Count dùng để lấy kích thước thực tế của tập hợp SortedList. Ví dụ sau đây sẽ hiển thị kích thước thực tế của objSortList:

Console.WriteLine( objSortList.Count );

3. IsFixedSize

Thuộc tính này dùng để chỉ ra kích thước của mảng có được đặt cố định hay không. Nếu kích thước được đặt cố định thì giá trị của thuộc tính là true, ngược lại là false. Ví dụ:

if (objSortList.IsFixedSize)
{
  Console.WriteLine("Yes");
}
else
{
  Console.WriteLine("No");
}

4. IsReadOnly

Thuộc tính này dùng để chỉ ra rằng mảng có thiết lập chế độ read-only hay không, nếu có thì giá trị của thuộc tính là true, ngược lại là false. Ví dụ:

if (objSortList.IsReadOnly)
{
  Console.WriteLine("True");
}
else
{
  Console.WriteLine("False");
}

5. Keys

Thuộc tính Keys là nơi chứa các key của SortedList. Keys được coi như một mảng đơn (một chiều) và nó cũng có một số phương thức cũng như thuộc tính riêng (chẳng hạn như Count, ToString(), GetType(), ...). Ví dụ dưới đây sẽ in ra tất cả các key của SortedList objSortList:

foreach (string key in objSortList.Keys)
  Console.WriteLine(key);

6. Values

Thuộc tính Values là nơi chứa danh sách các key của SortedList. Values cũng được coi là một mảng đơn (một chiều). Ví dụ dưới đây sẽ in ra tất cả các value của SortedList objSortList:

foreach (string value in objSortList.Values)
  
Console.WriteLine(value);

Ví dụ về SortedList

Ví dụ 1:

using System;
using System.Collections;
class SortedCollection
{
  static void Main(string[] args)
  {
    SortedList objSortList = new SortedList();
    objSortList.Add("John", "Administration");
    objSortList.Add("Jack", "Human Resources");
    objSortList.Add("Peter", "Finance");
    objSortList.Add("Joel", "Marketing");
    Console.WriteLine("Original values stored in the sorted list");
    Console.WriteLine("Key \t\t Values");
    for (int i = 0; i < objSortList.Count; i++)
    {
      Console.WriteLine(objSortList.GetKey(i) + " \t\t " + objSortList.GetByIndex(i));
    }
    if (!objSortList.ContainsKey("Jerry"))
    {
      objSortList.Add("Jerry", "Construction");
    }
    objSortList["Peter"] = "Engineering";
    objSortList["Jerry"] = "Information Technology";
    Console.WriteLine();
    Console.WriteLine("Updated values stored in hashtable");
    Console.WriteLine("Key \t\t Values");
    for (int i = 0; i < objSortList.Count; i++)
    {
      Console.WriteLine(objSortList.GetKey(i) + " \t\t " + objSortList.GetByIndex(i));
    }
  }
}

Trong ví dụ trên, phương thức Add() chèn các key và các value tương ứng vào trong thể hiện objSortList của SortedList, thuộc tính Count dùng để đếm số phần tử thực tế của danh sách; phương thức GetKey() sẽ trả về key tương ứng với chỉ số được chỉ ra trong đối số; phương thức GetByIndex() trả về value tương ứng với chỉ số được chỉ ra trong đối số của phương thức; phương thức ContainsKey() kiểm tra xem có key với giá trị "Jerry" ở trong danh sách hay không, nếu không có thì thêm key này vào danh sách với giá trị tương ứng là "Construction"; câu lệnh objSortList["Peter"] = "Engineering"; sẽ cập nhật value ứng với key "Peter" thành "Engineering"; câu lệnh objSortList["Jerry"] = "Information Technology"; sẽ cập nhật value ứng với key "Jerry" thành "Information Technology". Kết quả của ví dụ như sau:

sortedlist-example1

Ví dụ 2:

using System;
using System.Collections;
class Countries
{
  static void Main(string[] args)
  {
    SortedList objCountries = new SortedList();
    objCountries.Add("UK", "United Kingdom");
    objCountries.Add("GER", "Germany");
    objCountries.Add("USA", "United States of America");
    objCountries.Add("AUS", "Australia");
    Console.WriteLine("Read-only : " +
    objCountries.IsReadOnly);
    Console.WriteLine("Count : " + objCountries.Count);
    Console.WriteLine("List of countries:\n");
    Console.WriteLine("Country Code \t Name");
    for (int i = 0; i < objCountries.Count; i++)
    {
      Console.WriteLine(objCountries.GetKey(i) + "\t\t " + objCountries.GetByIndex(i));
    }
    objCountries.RemoveAt(1);
    Console.WriteLine("\nList of countries after removing element at index 1:\n");
    Console.WriteLine("Country Code \t Name");
    for (int i = 0; i < objCountries.Count; i++)
    {
      Console.WriteLine(objCountries.GetKey(i) + "\t\t " + objCountries.GetByIndex(i));
    }
  }
}

Trong ví dụ trên, phương thức Add() thêm các phần tử theo cặp key/value vào danh sách, phương thức IsReadOnly() kiểm tra xem có thay đổi được các value trong danh sách hay không. Phương thức GetByIndex() trả về value tại tương ứng với chỉ số (index) được chỉ ra. Phương thức RemoveAt() sẽ xóa phần tử tại vị trí chỉ số được chỉ ra (là đối số của phương thức). Kết quả của ví dụ được thể hiện ở hình dưới đây.

sortedlist-example2

Practical 6

EXERCISE 1: PHONEBOOK MANAGEMENT SYSTEM

1. Create an interface IPhoneBook contains these methods:

- void InsertPhone(string name, string  phone)

- void RemovePhone(string name)

- void UpdatePhone(string name, string  newphone)

- void SearchPhone(string name)

- void Sort()

2. Create class PhoneBook implements interface IPhoneBook

- Create a SortedList object to store data

- Method InsertPhone:

   If the name is new, add the name and phone to the phonebook

   If the name already exists, add new phone after old phone

   ( Ex: “0912333333 : 0902345671” )

- Method RemovePhone:

   Remove phone from the sorted list.

- Method UpdatePhone:

   Replace old phone with new phone

- Method SearchPhone:

   Search the phone of input name

- Method Sort:

   Sort items in the phonebook by name

3. Create Menu:

PHONEBOOK MANAGEMENT SYSTEM

1. Insert Phone

2. Remove Phone

3. Update Phone

4. Search Phone

5. Sort

6. Exit


EXERCISE 2: FOOTBALL MANAGEMENT SYSTEM

1. Create interface IPlayer that has some properties and methods:

Properties:

- Name ( string)

- Age (int, 0 < Age < 40)

- Attack (int, 0 < Attack < 100)

- Defense (int, 0 < Defense < 100)

- Stamina (int, 0 < Stamina < 100)

- Speed (int, 0 < Speed < 100)

- Power (int, 0 < Power < 100)

Methods:

- Void GetInfo()

2. Create class Player implements interface Iplayer

- Implements GetInfo method to print to the console the Name, Age and average point of player :( Attack + Defense +Stamina + Speed + Power) / 5

3. Create delegate ActionDelegate

- void ActionDelegate();

4. Create generic class Club<T> implement interface IEnumerable<T> which T is interface IPlayer or T is a class that implements interface IPlayer.

Event:

- void ActionDelegate  AttackEvent;

- void ActionDelegate DefenseEvent;

Methods:

- void Add(T t):

Add player to the list.

If player has the average point above 80, register method GetInfo of player to AttackEvent.

If player has the average point above 60 and less than 80, register method GetInfo of player to DefenseEvent.

5. Create Menu:

   1. Create new Player

   2. List all players

   3. Search player by Name

   4. Raise Attack Event

   5. Raise Defense Event

   6. Exit


EXERCISE 3:

1. Create a class name Student with these properties:

- static int Count : To count number of student objects created (Field)

- int ID : Student ID

- string Name : Student Name

- string Class : Student Class

- float Math : Mathematical mark

- float Physic : Physical mark

- float Chemistry : Chemistry mark

and a method:

- void Display() : Display student Name, class and the student’s average mark

2. Main program:

a. Display the menu:

1. Add a student
2. List students
3. Classify students
4. Exit

b. Create a List<Student> list to keep all students will be added

c. If user enters 1:

   i.Create a new Student object instance

   ii.Ask for Name, Class, Math, Physic, Chemistry …

   iii.Add the new student to the students list

d. If user enters 2:

   i.Loop in the students list and execute Display method

e. If user enters 3:

   i.Find all the students with average mark >=8.5 to a List<Student> list and named Goods
   Hint: Create a Predicate function IsGood

   ii.Find all the students with average mark >=7.0 and <8.5 to a List<Student> list and named Fairs
   Hint: Create a Predicate function IsFair

   iii.Find all the students with average mark >=5.0 and <7.0 to a List<Student> list and named Normals
   Hint: Create a Predicate function IsNormal

   iv.Find all the students with average mark <5.0 to a List<Student> list and named Bads
   Hint: Create a Predicate function IsBad

   v.Loop all elements in each Goods, Fairs, Normals, Bads list and execute student Display method

http://v1study.com/public/images/article/csharp-practical8.png

f. If user enters 4: Exit the program

Dictionary generic

Tổng quan

Namespace System.Collections.Generic chứa một số lượng lớn các tập hợp generic, một trong những tập hợp phổ biến nhất là lớp Dictionary generic, lớp này chứa một tập hợp các phần tử generic được tổ chức thành các cặp key/value, thông qua mỗi key ta sẽ lấy được value tương ứng. Không giống như namespace System.Collections, Dictionary generic được dùng để tạo một tập hợp các dữ liệu cùng kiểu và kiểu do người dùng quyết định. Mỗi phần tử ta thêm vào đều sẽ  bao gồm một value và một key tương ứng, và ta có thể truy xuất value thông qua key tương ứng đó.

Lớp Dictionary generic không cho phép chứa phần tử có key là null.

Lớp Dictionary generic thuộc namespace: System.Collections.Generic .

Lớp Dictionary generic có các thuộc tính và phương thức cho phép ta thêm cũng như thao tác với các phần tử trong tập hợp.

Cú pháp khai báo Dictionary generic

Dictionary<TKey, TValue> dictionary_name = new Dictionary<TKey, TValue>();

, trong đó, TKey và TValue lần lượt là kiểu của key và value sẽ được lưu vào Dictionary generic. Điểu này có nghĩa bạn chỉ có thể thêm được các phần tử có key và value tương ứng cùng kiểu đã được chỉ ra.

Ví dụ:

Dictionary<int, string> objDictionary = new Dictionary<int,string>();

Các phương thức của lớp Dictionary generic:

1. Add()

Thêm một phần tử với key và value cụ thể vào tập hợp. Ví dụ:

objDictionary.Add(25, "O dia cung");
objDictionary.Add(30, "Bo xu ly");
objDictionary.Add(15, "Bo mach chu");
objDictionary.Add(65, "Bo nho");

2. Remove()

Xoá một phần tử có key tương ứng với đối số của phương thức khỏi dictionary. Ví dụ:

objDictionary.Remove(65);

3. ContainsKey()

Kiểm tra xem key tương ứng với đối số của phương thức có nằm trong dictionary hay không. Ví dụ:

if(objDictionary.ContainsKey(65))
{
  Console.WriteLine("65: " + objDictionary[65]);
}
else
{
  Console.WriteLine("Khong chua");
}

4. ContainsValue()

Kiểm tra xem value tương ứng với đối số của phương thức có nằm trong dictionary hay không. Ví dụ:

if (objDictionary.ContainsValue("Bo nho"))
{
  Console.WriteLine("Yes");
}
else
{
  Console.WriteLine("No");
}

5. GetEnumerator()

Phương thức này sẽ trả về bộ liệt kê interface IDictionaryEnumerator thông qua các phần tử của dictionary và bạn có thể dùng để liệt kê (iterate) các phần tử trong danh sách. Ví dụ:

IDictionaryEnumerator objDictionayEnumerator = objDictionary.GetEnumerator();

6. GetType()

Phương thức này dùng để lấy kiểu của thể hiện hiện thời. Ví dụ:

Console.WriteLine(objDictionary.GetType());

Kết quả thực thi câu lệnh trên là như sau:

System.Collections.Generic.Dictionary`2[System.Int32,System.String]

Các thuộc tính của Dictionary generic

1. Count

Thuộc tính này dùng để lấy tổng số cặp key/value trong System.Collections.Generic.Dictionary<TKey, TValue>. Ví dụ:

Console.WriteLine(objDictionary.Count);

2. Item

Đây là thuộc tính mặc định, thuộc tính này dùng để lấy value hoặc gán value mới cho phần tử có key được chỉ ra. Ví dụ:

if (objDictionary.ContainsKey(65))
{
  Console.WriteLine("Gia tri cua key 65: " + objDictionary[65]);
  objDictionary[65] = "Bo luu tru du lieu";
  Console.WriteLine("Sau khi cap nhat, gia tri cua key 65 la: " + objDictionary[65]);
}

Trong ví dụ trên , nếu key = 65 (trong cặp ngoặc vuông) tồn tại thì thuộc tính mặc định Item sẽ được gọi tới và nó sẽ lấy value tương ứng với key để hiển thị, sau đó Item sẽ gán value ứng với key = 65 thành "Bo luu tru du lieu" rồi hiển thị lại.

3. Keys

Cũng giống như Hashtable hay SortedList, thuộc tính Keys của Dictionary generic như là mảng một chiều và nó chứa các key của danh sách, Keys có thuộc tính Count dùng để lấy số lượng key; Keys cũng có một số phương thức riêng của nó như CopyTo() dùng để copy các value tới một mảng khác, phương thức GetType() dùng để lấy kiểu của Keys, ... Ví dụ:

foreach (object key in objDictionary.Keys)
{
  Console.WriteLine(key);
}

4. Values

Thuộc tính Values là một mảng đơn chứa các value của danh sách, và Values cũng có những thuộc tính và phương thức với ý nghĩa tương tự như thuộc tính Keys. Ví dụ:

foreach (object value in objDictionary.Values)
{
  Console.WriteLine(value);
}

Ví dụ về Dictionary generic

Ví dụ 1:

using System;
using System.Collections;
using System.Collections.Generic;
class DictionaryCollection
{
  static void Main(string[] args)
  {
    Dictionary<int, string> objDictionary = new Dictionary<int, string>();
    objDictionary.Add(25, "Hard Disk");
    objDictionary.Add(30, "Processor");
    objDictionary.Add(15, "MotherBoard");
    objDictionary.Add(65, "Memory");
    ICollection objCollect = objDictionary.Keys;
    Console.WriteLine("Original values stored in the collection");
    Console.WriteLine("Keys \t Values");
    Console.WriteLine("----------------");
    foreach (int i in objCollect)
    {
      Console.WriteLine(i + " \t " + objDictionary[i]);
    }
    objDictionary.Remove(65);
    Console.WriteLine();
    if (objDictionary.ContainsValue("Memory"))
    {
      Console.WriteLine("Value Memory could not be deleted");
    }
    else
    {
      Console.WriteLine("Value Memory deleted successfully");
    }
    Console.WriteLine();
    Console.WriteLine("Values stored after removing element");
    Console.WriteLine("Keys \t Values");
    Console.WriteLine("----------------");
    foreach (int i in objCollect)
    {
      Console.WriteLine(i + " \t " + objDictionary[i]);
    }
  }
}

Trong ví dụ trên, lớp Dictionary tạo một thể hiện với tên objDictionary gồm hai tham số kiểu int và string, trong đó kiểu int sẽ là kiểu của key của phần tử sẽ được chèn vào và kiểu string sẽ là kiểu của value của phần tử; phương thức Add() sẽ chèn phần tử theo cặp key/value vào thể hiện objDictionary; thuộc tính Keys chứa danh sách các key của thể hiện objDictionary và nó cung cấp danh sách các key đó cho thể hiện objCollect của của interface ICollection; interface ICollection xác định kích thước và đồng bộ hóa các phương thức để thao tác với các key; phương thức Remove() sẽ xóa phần tử có value = "Memory" thông qua key = 65; phương thức ContainsValue() sẽ kiểm tra xem value "Memory" có tồn tại trong thể hiện objDictionary hay không và đưa ra thông báo thích hợp. Hình dưới đây thể hiện kết quả của ví dụ:

dictionary-generic-example1

Ví dụ 2:

using System;
using System.Collections;
using System.Collections.Generic;
class Car
{
  static void Main(string[] args)
  {
    Dictionary<int, string> objDictionary = new
    Dictionary<int, string>();
    objDictionary.Add(201, "Gear Box");
    objDictionary.Add(220, "Oil Filter");
    objDictionary.Add(330, "Engine");
    objDictionary.Add(305, "Radiator");
    objDictionary.Add(303, "Steering");
    Console.WriteLine("Dictionary class contains values of type");
    Console.WriteLine(objDictionary.GetType());
    Console.WriteLine("Keys \t\t Values");
    Console.WriteLine("__________________________");
    IDictionaryEnumerator objDictionayEnumerator = objDictionary.GetEnumerator();
    while (objDictionayEnumerator.MoveNext())
    {
      Console.WriteLine(objDictionayEnumerator.Key.ToString() + "\t\t" + objDictionayEnumerator.Value);
    }
  }
}

Trong ví dụ trên, phương thức GetEnumerator() của objDictionary sẽ trả về một enumerator là một tập hợp các phần tử của objDictionary  cho thể hiện objDictionayEnumerator của interface IDictionaryEnumerator, sau đó thông qua vòng lặp while kết hợp với phương thức MoveNext() của thể hiện objDictionayEnumerator để hiện thị các phần tử với cặp key/value tương ứng. Kết quả của ví dụ:

dictionary-generic-example2

Generic

Tổng quan

Generic là loại cấu trúc dữ liệu có tham số. Generic có thể làm việc với cả loại dữ liệu kiểu giá trị và loại dữ liệu kiểu tham chiếu; kiểu dữ liệu của generic khi định nghĩa sẽ là ở dạng chung chung, chưa cụ thể, và chính điều này sẽ giúp ích rất nhiều trong việc xây dựng những project có tính đơn giản hơn mà vẫn đạt hiệu quả tương ứng. Ta có thể định nghĩa lớp, interface, structure, phương thức và cả delegate như là kiểu generic trong C#.

Xét một chương trình C# sử dụng một biến mảng có kiểu Object để lưu trữ một danh sách tên của các sinh viên. Tên của sinh viên được đọc từ console có kiểu giá trị và được box để có thể lưu trữ theo kiểu Object. Trong trường hợp này trình biên dịch sẽ không thể xác minh được dữ liệu lưu trữ có cùng kiểu với kiểu dữ liệu mà bạn đã gán hay không, chẳng hạn như nếu bạn nhập vào dữ liệu là một số dữ liệu đó sẽ được box mà không được xác minh. 

Để đảm bảo an toàn kiểu và khắc phục được điều trên sử dụng generic sẽ là một lựa chọn; khi đó generic sẽ tạo ra một kiểu dữ liệu chung chung để rồi sẽ cụ thể hóa kiểu dữ liệu đó trong những tình huống cụ thể của bài toán.

Các Namespace, Class và Interface generic

Có một số namespace trong .NET Framework rất thuận tiện cho việc tạo và sử dụng các generic; ví dụ như namespace System.Collections.ObjectModel cho phép bạn tạo các tập hợp generic động và chỉ đọc, namespace System.Collections.Generic bao gồm các lớp và interface cho phép bạn định nghĩa các tập hợp generic tùy chỉnh, ...

Các lớp generic

Namespace System.Collections.Generic có chứa các lớp cho phép bạn tạo các tập hợp với kiểu an toàn; bảng dưới đây sẽ liệt kê các lớp được sử dụng phổ biến của namespace này:

Lớp Mô tả
Comparer Là một lớp trừu tượng cho phép ta tạo một tập hợp generic bằng cách thực thi các chức năng của interface IComparer
Dictionary.KeyCollection Bao gồm các key của thể hiện của lớp Dictionary
Dictionary.ValueCollection Bao gồm các value của thể hiện của lớp Dictionary
EqualityComparer Là một lớp trừu tượng cho phép ta tạo một tập hợp generic bằng cách thực thi các chức năng của interface IEqualityComparer

Các interface generic

Namespace System.Collections.Generic cũng chứa các interface cho phép bạn tạo các tập hợp với kiểu an toàn; bảng dưới đây sẽ liệt kê các lớp được sử dụng phổ biến của namespace này:

Interface Mô tả
IComparer Định nghĩa phương thức generic Comparer() dùng để so sánh các giá trị trong một tập hợp
INumerable Định nghĩa phương thức GetEnumerator() dùng để lặp (iterate) trên một tập hợp
IEqualityComparer Bao gồm các phương thức cho phép kiểm tra sự tương xứng giữa hai đối tượng bất kỳ

System.Collections.ObjectModel

Namespace này bao gồm các lớp có thể được dùng để tạo các tập hợp generic tùy chỉnh; bảng dưới đây sẽ liệt kê một số lớp của namespace này:

Lớp Mô tả
Collection<> Cung cấp lớp cơ sở cho các tập hợp generic
KeyedCollection<> Cung cấp một lớp trừu tượng cho một tập hợp với key và value tương ứng
ReadOnlyCollection<> Là một lớp generic cơ sở chỉ đọc dùng để ngăn ngừa sự thay đổi (modify) của tập hợp

Dưới đây là một ví dụ về việc sử dụng lớp ReadOnlyCollection<>:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
class ReadOnly
{
  static void Main(string[] args)
  {
    List<string> objList = new List<string>();
    objList.Add("Francis");
    objList.Add("James");
    objList.Add("Baptista");
    objList.Add("Micheal");
    ReadOnlyCollection<string> objReadOnly = new ReadOnlyCollection
    <string>(objList);
    Console.WriteLine("Values stored in the read only collection");
    foreach (string str in objReadOnly)
    {
      Console.WriteLine(str);
    }
    Console.WriteLine();
    Console.WriteLine("Total number of elements in the read only collection: " + objReadOnly.Count);
    if (objList.Contains("Francis"))
    {
      objList.Insert(2, "Peter");
    }
    Console.WriteLine("\nValues stored in the list after modification");
    foreach (string str in objReadOnly)
    {
      Console.WriteLine(str);
    }
    string[] array = new string[objReadOnly.Count * 2];
    objReadOnly.CopyTo(array, 5);
    Console.WriteLine("\nTotal number of values that can be stored in the new array: " + array.Length);
    Console.WriteLine("Values in the new array");
    foreach (string str in array)
    {
      if (str == null)
      {
        Console.WriteLine("null");
      }
      else
      {
        Console.WriteLine(str);
      }
    }
  }
}

Trong ví dụ trên, phương thức Main() của lớp ReadOnly tạo thể hiện có tên objList với một tham số kiểu string của lớp generic List; phương thức Add() sẽ thêm các phần tử vào thể hiện objList; phương thức Main() cũng tạo thể hiện có tên objReadOnly của lớp ReadOnlyCollection với một tham số kiểu string và các phần tử của thể hiện objList của lớp List sẽ được copy tới thể hiện này; phương thức Contains() sẽ kiểm tra xem thể hiện objList của lớp List có chứa phần tử với giá trị "Francis" hay không, nếu chứa thì sẽ thêm một phần tử mới có giá trị "Peter" vào objList tại vị trí có chỉ số là 2; phương thức Main() cũng tạo một biến mảng có tên array và có kích thước gấp đôi kích thước của thể hiện objReadOnly; phương thức CopyTo() sẽ tiến hành copy các phần tử của thể hiện objReadOnly vào mảng array và đặt các phần tử copy được vào vị trí có chỉ số bằng 5 trở đi. Kết quả của ví dụ trên được thể hiện ở hình dưới đây:

ObjectModel namespace

Tạo kiểu generic

Việc khai báo một generic luôn luôn phải có ít nhất một tham số kiểu và tham số kiểu sẽ được dùng để làm nơi giữ chỗ cho kiểu dữ liệu cụ thể sau này; kiểu dữ liệu cụ thể chỉ có thể được xác định khi kiểu generic được tham chiếu tới hoặc được khởi tạo trong chương trình.

Quá trình tạo một kiểu generic bắt đầu bằng việc định nghĩa kiểu generic với các tham số kiểu đi kèm; việc định nghĩa này đóng vai trò như là một bản thiết kế. Sau đó, các kiểu generic sẽ được tạo từ định nghĩa bằng cách xác định kiểu thực sự và sẽ thay thế cho các tham số kiểu hay các nơi giữ chỗ.

Ưu điểm của generic

Generic đảm bảo tính an toàn kiểu trong thời gian biên dịch chương trình. Generic cho phép ta dùng lại được các mã lệnh một cách an toàn mà không cần phải gán hay boxing. Mỗi định nghĩa kiểu generic có thể được sử dụng lại với các kiểu khác nhau nhưng tại một thời điểm chỉ sử dụng được một kiểu; cùng với khả năng sử dụng lại thì generic còn có một số khả năng nữa như sau:

· Tăng hiệu năng của chương trình, bởi vì chương trình sẽ sử dụng ít bộ nhớ hơn do không cần phải sử dụng các biện pháp gán hay boxing.

· Đảm bảo được mô hình lập trình định kiểu mạnh mẽ.

· Giảm được lỗi run-time có thể gây ra bởi không cần sử dụng đến gán và boxing.

Tạo và sử dụng generic

Trong C# ta có thể áp dụng generic cho cả lớp, interface, phương thức và delegate. Phần này sẽ ta sẽ tìm hiểu cách tạo và sử dụng những generic này.

Lớp generic

Các lớp generic định nghĩa những chức năng mà có thể được sử dụng cho bất kỳ loại dữ liệu nào, chúng được khai báo bằng cách sử dụng từ khóa class và theo sau là một tên lớp, phía sau đó là cặp ngoặc nhọn và bên trong có chứa tham số kiểu. Khi khai báo lớp generic ta nên đưa ra giới hạn hay ràng buộc cho tham số kiểu bằng cách sử dụng từ khóa where. Lưu ý rằng đây chỉ là một tuỳ chọn, vì vậy, khi tạo loại lớp này bạn phải khái quát hoá các kiểu dữ liệu vào phần tham số kiểu và tuỳ chọn này sẽ quyết định ràng buộc để áp dụng trên tham số kiểu.

Cú pháp:

Bổ_từ_truy_cập class Tên_lớp<Danh_sách_tham_số_kiểu> [where Mệnh_đề_ràng_buộc_tham_số_kiểu]

, trong đó:

Bổ_từ_truy_cập: dùng để xác định tầm vực của lớp generic (đây là một tuỳ chọn).

Danh_sách_tham_số_kiểu: được dùng tạo nơi giữ chỗ cho kiểu dữ liệu thực sự sau này.

Mệnh_đề_ràng_buộc_tham_số_kiểu: đây là một tuỳ chọn, dùng để thiết lập ràng buộc tham số kiểu, có thể kiểu là class (không được là sealed class) hoặc interface áp dụng cho tham số kiểu cùng với từ khoá where (ví dụ như where T : EIDemo).

Ví dụ:

using System;
using System.Collections;
using System.Collections.Generic;
class ChungChung<T>
{
  T[] giaTri;
  int _boDem = 0;
  public ChungChung(int max)
  {
    giaTri = new T[max];
  }
  public void Them(T val)
  {
    if (_boDem < giaTri.Length)
    {
      giaTri[_boDem] = val;
      _boDem++;
    }
  }
  public void HienThi()
  {
    Console.WriteLine("Kieu cua lop co cau truc la: " + typeof(T));
    Console.WriteLine("Cac gia tri luu trong doi tuong cua lop co cau truc la:");
    for (int i = 0; i < giaTri.Length; i++)
    {
      Console.WriteLine(giaTri[i]);
    }
  }
}
class SinhVien
{
  static void Main(string[] args)
  {
    ChungChung<string> objCC1 = new ChungChung<string>(3);
    objCC1.Them("Phuong");
    objCC1.Them("Binh");
    objCC1.HienThi();
    ChungChung<int> objCC2 = new ChungChung<int>(2);
    objCC2.Them(325);
    objCC2.Them(57);
    objCC2.HienThi();
  }
}

Phân tích ví dụ trên:

Trước tiên định nghĩa một lớp generic có tên ChungChung và có tham số kiểu là T; bên trong lớp này có một hàm tạo với một tham số kiểu int; phương thức Them() có một tham số cùng kiểu với lớp generic; phương thức Display() hiển thị kiểu giá trị được xác định thông qua tham số kiểu và các giá trị được cung cấp bởi người dùng thông qua các đối tượng; phương thức Main() của lớp SinhVien tạo một thể hiện có tên objGeneral của lớp ChungChung bằng cách cung cấp giá trị tham số kiểu là string và tổng số các giá trị được lưu trữ là 3; thể hiện objCC1 gọi phương thức Them() và truyền đi tên của các sinh viên và các tên sinh viên sẽ được hiển thị thông qua lời gọi phương thức HienThi(). Sau đó, thể hiện objCC2 được tạo với tham số kiểu là int và khởi tạo tổng số giá trị lưu trữ là 2. Đến đây bạn sẽ thấy một điều rằng ta không cần phải thay đổi mã lệnh để phù hợp với kiểu dữ liệu mới khi ta sử dụng lớp generic.

Lưu ý: Lớp generic có thể được lồng bên trong một lớp generic hoặc không generic khác, và bất kỳ lớp nào lồng trong một lớp generic thì bản thân nó được coi là lớp generic vì các tham số kiểu của lớp bên ngoài nó cũng áp dụng được cho nó.

Ràng buộc cho các tham số kiểu

Ta có thể sử dụng các ràng buộc cho kiểu tham số khi khai báo kiểu generic. Mỗi ràng buộc sẽ là một giới hạn áp đặt cho kiểu dữ liệu của tham số kiểu. Ràng buộc được tạo thông qua sử dụng từ khoá where và việc thiết lập ràng buộc cho tham số kiểu sẽ đảm bảo tính nhất quán và độ tin cậy của dữ liệu trong tập hợp.

Bảng dưới đây sẽ liệt kê các loại ràng buộc ta có thể được áp dụng cho tham số kiểu (giả sử T là tên của tham số kiểu):

Ràng buộc Mô tả
T : struct Tham số kiểu phải là một kiểu giá trị (value type) và chấp nhận giá trị null
T : class Tham số kiểu phải là một kiểu tham chiếu (reference type), ví dụ như class, interface, delegate
T : new() Tham số kiểu phải bao gồm một hàm tạo không tham số và có bổ từ truy cập là public
T : Tên_lớp Tham số kiểu phải là một lớp cơ sở hoặc dược thừa kế từ lớp cơ sở
T : Tên_interface Tham số kiểu phải là một interface hoặc được thừa kế từ interface

Ví dụ dưới đây sẽ tạo một lớp generic với ràng buộc tham số kiểu là một class:

using System;
using System.Collections.Generic;
class NhanVien
{
  string _tenNV;
  int _idNV;
  public NhanVien(string ten, int id)
  {
    _tenNV = ten;
    _idNV = id;
  }
  public string Ten
  {
    get
    {
      return _tenNV;
    }
  }
  public int ID
  {
    get
    {
      return _idNV;
    }
  }
}
class Demo<T> where T : NhanVien
{
  T[] _ten = new T[3];
  int _boDem = 0;
  public void Them(T val)
  {
    _ten[_boDem] = val;
    _boDem++;
  }
  public void HienThi()
  {
    for (int i = 0; i < _boDem; i++)
    {
      Console.WriteLine(ten[i].Ten + ", " + _ten[i].ID);
    }
  }
}
class TestDemo
{
  static void Main(string[] args)
  {
    Demo<NhanVien> objDemo = new Demo<NhanVien>();
    objDemo.Them(new Employee("Lan", 123));
    objDemo.Them(new Employee("Long", 456));
    objDemo.Them(new Employee("Minh", 789));
    objDemo.HienThi();
  }
}

Trong ví dụ trên, lớp Demo được tạo với tham số kiểu là T và ràng buộc tham số kiểu cho T có kiểu class là lớp Employee; lớp Demo tạo một biến mảng kiểu T, tức là kiểu NhanVien; phương thức Them() của lớp Demo có một tham số cũng là kiểu T và nó sẽ nhận lại giá trị từ lời gọi phương thức Them() ở phương thức Main(), điều này có nghĩa tham số của phương thức Them()Main() phải có kiểu NhanVien và hàm tạo của Demo được gọi ngay trong tham số của phương thức Them() của Main(). Dưới đây là kết quả của ví dụ:

Lan, 123
Long, 456
Minh, 789

Chú ý: Khi ta sử dụng một kiểu cụ thể nào đó làm ràng buộc thì kiểu đó cần phải có mức truy cập cao hơn kiểu generic mã sẽ được áp dụng ràng buộc.

Thừa kế lớp generic

Lớp generic có thể được thừa kế giống như các lớp khác trong C#, điều này có nghĩa rằng lớp generic có thể đóng vai trò là lớp cơ sở hoặc lớp dẫn xuất.

Ta có quyền thừa kế các tham số kiểu từ lớp cha là lớp generic, điều này sẽ tránh được phải truyền đối số với kiểu dữ liệu cụ thể cho tham số. Lưu ý là nếu lớp dẫn xuất là lớp không generic thì trong lớp dẫn xuất ta phải cung cấp kiểu dữ liệu của tham số để thế vào tham số kiểu generic của lớp cơ sở, còn nếu lớp dẫn xuất là lớp generic thì ràng buộc áp đặt tại lớp cơ sở phải được đưa vào trong lớp generic dẫn xuất đó.

· Ví dụ khai báo một lớp generic thừa kế từ lớp generic khác:

// Generic thừ kế Generic
public class SinhVien<T>
{
}
public class DiemSo<T> : SinhVien<T>
{
}

· Ví dụ khai báo một lớp không phải generic thừa kế từ lớp generic:

// Non-Generic thừa kế Generic
public class Student<T>
{
}
public class Mark : Student<string>
{
}          

Phương thức generic

Chúng xử lý các dữ liệu trong đó kiểu của chúng chỉ được biết khi truy cập vào các biến chứa những dữ liệu đó, chúng được khai báo với các tham số kiểu nằm trong cặp ngoặc nhọn. Việc định nghĩa các phương thức như vậy sẽ giúp ta có thể truyền các kiểu dữ liệu khác nhau mỗi lần gọi. Ta có thể khai báo phương thức generic trong một lớp generic hoặc non-generic, và khi chúng được khai báo bên trong lớp generic thì phần thân của chúng sẽ tham chiếu được đến cả các tham số kiểu của chúng lẫn của lớp chứa chúng.

Phương thức generic có thể được khai báo cùng với những từ khoá sau:

virtual: Từ khoá này sẽ giúp phương thức được ghi đè trong lớp dẫn xuất.

override: Từ khoá này sẽ giúp phương thức ghi đè phương thức của lớp cơ sở.

abstract: Phương thức chứa từ khoá này chỉ có thể được khai báo mà không được định nghĩa (không có phần thân).

Cú pháp khai báo phương thức generic:

Bổ_từ_truy_cập Kiểu_trả_về Tên_phương_thức<Danh_sách_tham_số_kiểu>(Danh_sách_tham_số)

Ví dụ:

using System;
using System.Collections.Generic;
class HoanViSo
{
  static void HoanVi<T>(ref T so1, ref T so2)
  {
    T tam = so1;
    so1 = so2;
    so2 = tam;
  }
  static void Main(string[] args)
  {
    int so1 = 67;
    int so2 = 89;
    Console.WriteLine("Cac gia tri truoc khi hoan vi, so1 = " + so1 + ", so2 = " + so2);
    HoanVi<int>(ref so1, ref so2);
    Console.WriteLine("Sau khi hoan vi, so1 = " + so1 + ", so2 = " + so2);
  }
}

Giải thích ví dụ: Lớp HoanViSo có phương thức HoanVi() với tham số kiểu là T đặt trong cặp ngoặc nhọn và có hai đối số cũng có kiểu là T. Trong phương thức HoanVi() tạo một biến tên tam cũng có kiểu T và được gán giá trị của biến so1; phương thức Main() khai báo hai biến so1so2 có cùng kiểu int và khởi tạo giá trị so1 = 67so2 = 89, sau đó gọi đến phương thức HoanVi() và truyền đi kiểu int trong cặp ngoặc nhọn và hai tham chiếu của hai biến so1so2.

Interface generic

Loại interface này rất hữu ích đối với các lớp hoặc tập lớp generic trong việc thể hiện các mục trong tập hợp. Ta có thể sử dụng các lớp và interface generic để tránh các hoạt động boxingunboxing trên các kiểu dữ liệu. Các lớp generic có thể thực thi các interface generic bằng cách truyền các tham số cụ thể tới interface. Interface generic cũng có thể thừa kế.

Cú pháp khai báo interface generic:

interface Tên_interface <Danh_sách_tham_số_kiểu> [where Mệnh_đề ràng_buộc_tham_số_kiểu]
{
}

Ví dụ:

using System;
using System.Collections.Generic;
interface IToanHoc<T>
{
  T Cong(T valOne, T valTwo);
  T Tru(T valOne, T valTwo);
}
class ConSo : IToanHoc<int>
{
  public int Cong(int so1, int so2)
  {
    return so1 + so2;
  }
  public int Tru(int so1, int so2)
  {
    if (so1 > so2)
    {
      return (so1 - so2);
    }
    else
    {
      return (so2 - so1);
    }
  }
  static void Main(string[] args)
  {
    int so1 = 23;
    int so2 = 45;
    ConSo objCS = new ConSo();
    Console.Write("Cong {0} voi {1} duoc: ", so1, so2);
    Console.WriteLine(objCS.Cong(so1, so2));
    Console.Write("Tru {0} cho {1} duoc: ", so1, so2);
    Console.WriteLine(objCS.Tru(so1, so2));
  }
}

Giải thích ví dụ: Trong interface generic IToanHoc với tham số kiểu là T khai báo hai phương thức Cong() và Tru(), mỗi phương thức cũng đều có hai tham số có kiểu là T. Lớp ConSo thực thi IToanHoc bằng cách cung cấp kiểu int trong cặp ngoặc nhọn và thực thi hai phương thức của interface này. Phương thức Main() tạo một thể hiện của lớp ConSo và gọi hai phương thức Cong()Tru() để thực hiện các phép tính cộng và trừ. Kết quả của ví dụ như sau:

Cong 23 voi 45 duoc: 68
Tru 23 voi 45 duoc: 22

Ràng buộc tham số kiểu là interface generic

Ta có thể định nghĩa interface như là một tham số kiểu và điều này sẽ cho phép ta sử dụng các thành phần của interface trong một lớp generic. Mặt khác thì điều này cũng đảm bảo rằng chỉ có kiểu là interface mới được sử dụng trong lớp. Một lớp có quyền có nhiều tham số kiểu là interface. Ví dụ:

using System;
using System.Collections.Generic;
interface IChiTiet
{
  void ChiTiet();
}
class SinhVien : IChiTiet
{
  string _ten;
  int _id;
  public Student(string ten, int id)
  {
    _ten = ten;
    _id = id;
  }
  public void ChiTiet()
  {
    Console.WriteLine(_id + "\t" + _ten);
  }
}
class ThuNghiem<T> where T : IChiTiet
{
  T[] _giaTri = new T[3];
  int _boDem = 0;
  public void Them(T val)
  {
    _giaTri[_boDem] = val;
    _boDem++;
  }
  public void HienThi()
  {
    for (int i = 0; i < 3; i++)
    {
      _giaTri[i].ChiTiet();
    }
  }
}
class Demo
{
  static void Main(string[] args)
  {
    ThuNghiem<SinhVien> objTN = new ThuNghiem<SinhVien>();
    objTN.Them(new SinhVien("Xuan Truong", 123));
    objTN.Them(new SinhVien("Ngoc Anh", 456));
    objTN.Them(new SinhVien("Dinh Tung", 789));
    objTN.HienThi();
  }
}

Giải thích ví dụ: Trong interface IChiTiet khai báo phương thức ChiTiet(). Lớp SinhVien thực thi interface IChiTiet. Lớp ThuNghiem được tạo với tham số kiểu là T với ràng buộc tham số kiểu là IChiTiet, điều này có nghĩa rằng tham số kiểu chỉ có thể là IChiTiet. Phương thức Main() tạo một thể hiện của ThuNghiem bằng cách truyền giá trị của tham số kiểu là SinhVien vì lớp này thực thi interface IChiTiet.

Iterator

Giới thiệu

Iterator trong C# được dùng để duyệt qua một danh sách các giá trị của tập hợp. Iterator là một khối mã lệnh trong đó thường dùng đến vòng lặp foreach để tham chiếu liên tiếp tới tập hợp các giá trị. Ví dụ như nếu muốn sắp xếp các giá trị của tập hợp ta cần sử dụng iterator để lấy lần lượt từng giá trị một rồi đem so sánh.

Iterator không phải là một thành phần dữ liệu, nó chỉ là cách truy cập các thành phần, chẳng hạn như là một phương thức (bộ truy cập get chẳng hạn) hoặc một toán tử cho phép ta điều hướng thông qua các giá trị của tập hợp. Iterator sẽ xác định cách mà các giá trị được tạo ra khi vòng lặp foreach truy cập vào các phần tử của tập hợp, và iterator sẽ lưu vết của các phần tử đó, điều này sẽ giúp tăng tốc độ truy cập các giá trị của các phần tử.

Ưu điểm của iterator

· Iterator cung cấp một cách đơn giản và nhanh hơn cách thức duyệt (lặp) trên các giá trị của tập hợp.

· Iterator sẽ giảm đi tính phức tạp bằng việc cung cấp bộ liệt kê cho tập hợp.

· Iterator có thể trả về một số lượng lớn các giá trị.

· Iterator có thể được dùng để định lượng các giá trị và chỉ trả lại những giá trị cần thiết.

· Iterator có thể trả lại những giá trị mà không hề dùng đến bộ nhớ bằng cách tham chiếu đến từng giá trị trong tập hợp.

Thực thi iterator

Iterator có thể được tạo bằng cách thực thi phương thức GetEnumerator() của interface IEnumerator, phương thức này sẽ trả về một tham chiếu của interface này.

Khối iterator sẽ sử dụng từ khoá yield để cung cấp các giá trị cho thể hiện của bộ liệt kê (enumerator) hoặc để kết thúc quá trình lặp, cụ thể, câu lệnh yield return sẽ trả về các giá trị, còn câu lệnh yield break sẽ kết thúc tiến trình lặp. Khi gặp câu lệnh yield return thì vị trí hiện thời sẽ được lưu trữ và lần kế khi iterator được gọi thì việc thực thi sẽ bắt đầu từ vị trí đã được lưu trữ đó. Ví dụ:

using System;
using System.Collections;
class Department : IEnumerable
{
  string[] departmentNames = { "Marketing", "Tai chinh", "Cong nghe thong tin", "Hanh chinh nhan su" };
  public IEnumerator GetEnumerator()
  {
    for (int i = 0; i < departmentNames.Length; i++)
    {
      yield return departmentNames[i];
    }
  }
  static void Main(string[] args)
  {
    Department objDepartment = new Department();
    Console.WriteLine("Ten cua cac phong ban");
    Console.WriteLine();
    foreach (string str in objDepartment)
    {
      Console.WriteLine(str);
    }
  }
}

Trong ví dụ trên, lớp Department thực thi interface IEnumerable, trong lớp có một biến mảng để lưu trữ tên của các phòng ban và thực thi phương thức GetEnumerator() của IEnumerable bên trong có vòng lặp for, vòng lặp này trả về các tên của các phòng ban ứng với mỗi vị trí chỉ số trong biến mảng; khối mã trong phương thức GetEnumerator() chính là sự thể hiện của iterator; phương thức thức Main() tạo một thể hiện của lớp Department và chứa một vòng lặp foreach để hiển thị tên của các phòng ban.

Dưới đây là kết quả thể hiện cho ví dụ trên:

Ten cua cac phong ban
Marketing
Tai chinh
Cong nghe thong tin
Hanh chinh nhan su

Lưu ý: Khi bắt gặp iterator thì trình biên dịch của C# sẽ gọi các phương thức Current(), MoveNext() và Dispose() (một cách mặc định); những phương thức này được dùng để duyệt các dữ liệu trong tập hợp.

Iterator generic

C# cho phép tạo iterator generic, công việc này được thực hiện bằng cách trả về một đối tượng có kiểu interface generic IEnumerator<T> hoặc IEnumerable<T>. Iterator generic có thể duyệt dữ liệu với kiểu bất kỳ. Ví dụ:

using System;
using System.Collections.Generic;
class GenericDepartment<T>
{
  T[] item;
  public GenericDepartment(T[] val)
  {
    item = val;
  }
  public IEnumerator<T> GetEnumerator()
  {
    foreach (T value in item)
    {
      yield return value;
    }
  }
}
class GenericIterator
{
  static void Main(string[] args)
  {
    string[] departmentNames = { "Marketing", "Finance", "Information Technology", "Human Resources" };
    GenericDepartment<string> objGeneralName = new GenericDepartment<string>(
    departmentNames);
    foreach (string val in objGeneralName)
    {
      Console.Write(val + "\t");
    }
    int[] departmentID = { 101, 110, 210, 220 };
    GenericDepartment<int> objGeneralID = new GenericDepartment<int>
    (departmentID);
    Console.WriteLine();
    foreach (int val in objGeneralID)
    {
      Console.Write(val + "\t\t");
    }
    Console.WriteLine();
  }
}

Trong ví dụ trên, lớp generic GenericDepartment được tạo với tham số kiểu là T, trong lớp có một biến mảng và một hàm tạo có tham số để khởi tạo giá trị cho các phần tử của mảng; trong lớp GenericDepartment còn có phương thức GetEnumerator() với kiểu trả về là IEnumerator, phương thức này trả lại các phần tử được lưu trong biến mảng thông qua câu lệnh yield return; phương thức Main() trong lớp GenericIterator tạo một thể hiện của lớp GenericDepartment để tham chiếu tới các tên phòng ban khác nhau của mảng; một đối tượng khác của lớp GenericDepartment cũng được tạo và nó tham chiếu tới các ID của các phòng ban của mảng.

Iterator được đặt tên

Iterator có thể được tạo bằng cách tạo một phương thức với tên riêng, phương thức này có kiểu trả về là interface IEnumerable, người ta gọi là iterator được đặt tên. Loại iterator này có thể chấp nhận các đối số mà có thể được dùng để quản lý điểm đầu và điểm cuối của vòng lặp foreach. Kỹ thuật này giúp ta lấy những giá trị theo ý muốn trong tâp hợp.

Sau đây là cú pháp cho kỹ thuật này:

Access_modifier  IEnumerable  Iterator_name(Parameter_list){}

Ví dụ:

using System;
using System.Collections;
class NamedIterators
{
  string[] cars = { "Ferrari", "Mercedes", "BMW", "Toyota", "Nissan" };
  public IEnumerable GetCarNames()
  {
    for (int i = 0; i < cars.Length; i++)
    {
      yield return cars[i];
    }
  }
  static void Main(string[] args)
  {
    NamedIterators objIterator = new NamedIterators();
    foreach (string str in objIterator.GetCarNames())
    {
      Console.WriteLine(str);
    }
  }
}

Trong ví dụ trên, lớp NamedIterators bao gồm một biến mảng và phương thức GetCarNames() (đây chính là iterator được đặt tên) có kiểu trả về IEnumerable, vòng lặp for của phương thức duyệt các giá trị trong biến mảng; phương thức Main() tạo một thể hiện của lớp NamedIterators và thể hiện này được dùng trong vòng lặp foreach để hiển thị tên của các xe hơi trong biến mảng.

Kết quả cho ví dụ trên được thể hiện như sau:

Ferrari
Mercedes
BMW
Toyota
Nissan

Practical 7

EXERCISE 1: USER MANAGEMENT SYSTEM

1. Create an interface called IUser in 2 files with these members:

IUserPro.cs:

a. Properties:

   int ID;                  => Index of the User, start from 1 to n
   string Name;        => User Name
   string Password;  => Password

b. Indexer:

   int? PhoneList

IUserMethod.cs:

   void Display()

2. Create a class name User

a. Implement interface IUser.

b. Indexer PhoneList implements a List<int?> type variable at the specified index. If the specified index is out of boudary, then return null.

c. The Display method will print Name, Password and all the phone numbers in PhoneList of User to console.

3. Build the menu:

1. Add User
2. Check User
3. Display all Users

4. If user chooses 1, then:

a. Create a new User object.

b. Set ID = Total users + 1.

c. Set Name = Read from console.

d. Set Password = Read from console.

e. Asks User to input number of phones and enter these phone to PhoneList.

f. Add the user to an Dictionary<int, IUser> with Key = UserID and Value = user.

5. If user chooses 2, then:

a. Ask user to enter ID.

b. Ask user to enter Name.

c. Ask user to enter Password.

d. Check in the Dictionary if the UserName and Password is match. If UserName and Password is match, show the message “User Checked” else show the message “User not found”.

6. If user chooses 3, then:

Display all Users and their phone numbers to the console using their Display method.


EXERCISE 2: MOVIE MANAGEMENT SYSTEM

In this exam, you’ll have to create a Movies Management system. The system allows input, list, search … movies.

1. Create an interface name IMovie  contains these properties and methods:

a. Properties:

ID (int)

Name (string)

PublishDate (DateTime)

 Director (string)

 Subtitle(string)

 AverageRate (float) – Read only property

b. Methods:

void Display()

2. Create a class name Movie:

a. Implements the IMovie interface.

b. The method Display will print all Name, PublishDate, Director, Language and AverageRate of the movie  to the console.

c. Declare an array name RateList type double has size of 3 elements.

d. Create an indexer uses the array RateList in step 2c.

e. Create a method named Calculate to set AverageRate = average of 3 double elements in AverageList array.

3. Create a class name CustomList that inherits from interface IEnumerable has some properties and methods:

a. private ArrayList MovieList;

b. public void Add(IMovie movie);

c. public void Remove(IMovie movie); //remove movie from MovieList

d. public void Sort(IComparer comp); //sort the items in MovieList with Icomparer comp

e. class CustomList allows user to iterate each item in MovieList.

4. Create a class name CustomSort that allows user sort IMovie items  in MovieList of class CustomList by their AverageList.

5. Display  a tasks menu to choose:

PLEASE AN OPTION:

   1. Insert new Movie
   2. View list of Movie
   3. Sort Movie by Average List
   4. Delete a movie
   5. Exit

6. If user type from keyboard then:

a. Create a new Movie  instance and input Name, PublishDate, Director, Language and then ask user to enter 3 rating points and set to the instance indexer:

- The ID is auto increament ( ID++ )

- Be sure to check format of PublishDate

b. Create a CustomList to keep the Movie instance in MovieList

7. If user type 2 from keyboard then:

Loop from all movie instances in the CustomList then executes the Display method and Calculate from Movie.

8. If user type 3 from keyboard then:

Sort all movie instances in the CustomList by  their AverageRate

9. While user not chooses Exit (type 5 from keyboard) then go back to the menu step 3 to ask user chooses an option.

 

Phương thức không tên

Tổng quan

Phương thức không tên (Anonymous method) là một khối mã lệnh không có tên và có thể được truyền như là một tham số delegate.

Thông thường thì delegate có thể gọi một hoặc nhiều phương thức có tên và được áp dụng trong khi khai báo delegate. Trước khi có phương thức không tên, nếu ta muốn truyền một khối lệnh nhỏ tới một delegate thì ta luôn phải tạo một phương thức chứa khối lệnh nhỏ đó và truyền phương thức đó tới delegate; nhưng khi áp dụng phương thức không tên thì đơn giản ta chỉ cần truyền đi khối mã lệnh bên trong tới delegate mà không phải đặt nó vào một phương thức có tên cụ thể (tạo phương thức thực sự).

Dưới đây là một ví dụ áp dụng phương thức không tên:

using System;
using System.Collections;
class Demo
{
  static void Main(string[] args)
  {
    System.Threading.Thread th = new System.Threading.Thread(
      delegate()
      {
        Console.WriteLine("Example about Anonimous Method!");
      });
    th.Start();
  }
}

Kết quả của ví dụ trên được thể hiện ở hình dưới đây:

http://v1study.com/public/images/article/csharp-anonymous-method-ex1.png

Đặc điểm của phương thức không tên

Phương thức không tên chỉ có thể được sử dụng thay cho phương thức có tên nếu phương thức không tên đó được gọi thông qua một delegate.

Mỗi phương thức không tên đều có những đặc điểm sau đây:

· Xuất hiên như một khối mã lệnh nội tuyến trong khai báo delegate.

· Là lựa chọn tốt nhất cho những khối mã lệnh nhỏ.

· Chấp nhận tham số với kiểu bất kỳ.

· Sử dụng được cả những tham số có kiểu generic

· Không được sử dụng những câu lệnh nhảy như goto, break, ... để chuyển quyền điều khiển ra ngoài phạm vi của phương thức.

Tạo phương thức không tên

Phương thức không tên được tạo khi bạn khởi tạo hoặc tham chiếu đến một delegate với một khối lệnh không tên. Những điều sau đây ta cần lưu ý khi tạo phương thức không tên:

· Ta có thể tạo phương thức không tên nằm bên ngoài hoặc bên trong phương thức có tên.

· Một khi đã xuất hiện từ khóa delegate trong thân của một phương thức thì bắt buộc delegate đó phải được theo sau bởi một khối mã lệnh không tên.

· Phương thức không tên được định nghĩa phải bao gồm cặp ngoặc xoắn ({}) khi tạo một đối tượng của delegate.

· Phương thức không tên không được có kiểu trả về.

· Phương thức không tên không được có bổ từ truy cập.

Cú pháp:

//Đầu tiên tạo một delegate trước
Access_modifier  delegate  Return_type Delegate_name(Parameters);
//Rồi sau đó mới tạo phương thức không tên
Delegate_name Delegate_instance = new Delegate_name(Parameters)
{
   Body_of_anonymous_method
};

, trong đó:

Access_modifier: Là bổ từ truy cập, bao gồm các bổ từ như public, protected, private, intarnal, protected intarnal.

Delegate_name: Là tên của delegate do ta tự đặt (đương nhiên phải tuân theo quy tắc đặt tên trong C#).

Đoạn mã dưới đây minh họa cách tạo và sử dụng phương thức không tên:

using System;
class AnonymousMethods
{
  delegate void Display();
  //Tạo phương thức không tên nằm bên ngoài phương thức có tên
  static Display objDisplay = delegate()
  {
    Console.WriteLine("This anonymous method is outside the named method.");
  };
  static void Main(string[] args)
  {
    //Tạo phương thức không tên nằm bên trong phương thức có tên
    Display objDisp = delegate()
    {
      Console.WriteLine("This anonymous method is inside the named method.");
    };
    objDisp();
  }
}

Kết quả của đoạn mã trên:

http://v1study.com/public/images/article/csharp-anonymous-method-ex2.png

Tham chiếu tới nhiều phương thức không tên

C# cho phép bạn tạo và khởi tạo một delegate để có thể tham chiếu tới nhiều phương thức không tên. Điều này được thực hiện bằng cách sử dụng toán tử +=. Toán tử += được dùng để bổ sung thêm các tham chiếu tới phương thức không tên hoặc có tên sau khi đã khởi tạo delegate. Ví dụ:

using System;
class MultipleAnonymousMethods
{
  delegate void Display();
  static void Main(string[] args)
  {
    //delegate được khởi tạo với một tham chiếu đến phương thức không tên
    Display objDisp = delegate()
    {
      Console.WriteLine("This illustrates one anonymous method");
    };
    //delegate được bổ sung thêm một phương thức không tên nữa
    objDisp += delegate()
    {
      Console.WriteLine("This illustrates another anonymous method with the same delegate instance");
    };
    objDisp();
  }
}

Kết quả của chương trình trên:

http://v1study.com/public/images/article/csharp-anonymous-method-add-reference-to-delegate.png

Biến ngoài (outer) của phương thức không tên

Ta có thể khai báo biến trong phương thức không tên, những biến này gọi là biến ngoài (outer). Các biến outer sẽ được 'chụp - capture' lại khi chúng được sử dụng đến; chúng sẽ được lưu trữ trong bộ nhớ cho đến khi delegate được thu dọn. Phạm vi hoạt động của các biến outer chỉ là nội trong phương thức không tên khai báo chúng.

Truyền tham số

C# cho phép truyền tham số tới phương thức không tên. Kiểu của các tham số được xác định ngay tại thời điểm khai báo delegate và các tham số được định nghĩa trong cặp ngoặc tròn sau tên của delegate. Phương thức không tên có quyền truy cập tất cả các tham số này giống như phương thức có tên. Ta có thể truyền giá trị cho các tham số của phương thức không tên khi delegate được gọi. Ví dụ:

using System;
class Parameters
{
  delegate void Display(string str, int num);
  static void Main(string[] args)
  {
    Display objDisp = delegate(string str, int num)
    {
      Console.WriteLine(str + num);
    };
    objDisp("Value = ", 100);
  }
}

Kết quả của ví dụ trên:

http://v1study.com/public/images/article/csharp-anonymous-method-passing-parameter.png

Lưu ý: Nếu việc định nghĩa phương thức không tên mà không bao gồm bất kỳ đối số nào thì bạn có thể sử dụng cặp ngoặc trống trong khai báo delegate. Phương thức không tên mà không có danh sách tham số thì chỉ có thể đi với những delegate không tham số.

Kiểm tra kiến thức của bạn

Câu nào sau đây là đúng khi nói về phương thức không tên trong C#?

(A): Phương thức không tên là các khối lệnh mà có thể được tích hợp với một cài đặt delegate.

(B): Các tham số của phương thức không tên luôn luôn có kiểu generic.

(C): Các phương thức không tên phù hợp với các khối lệnh nhỏ và lớn.

(D): Các biên được khai báo trong phương thức không tên được gọi là biến ngoài.

(E): Các giá trị của các tham số trong một phương thức không tên được truyền khi delegate được gọi.

Các lựa chọn cho bạn:

Phương án 1: (A), (D), (E)

Phương án 2: (B), (C)

Phương án 3:  (C), (E)

Phương án 4: (D)

Practical 8

1. Create a class name Student with these properties:

- static int Count : To count number of student objects created (Field)

- int ID : Student ID

- string Name : Student Name

- string Class : Student Class

- float Math : Mathematical mark

- float Physic : Physical mark

- float Chemistry : Chemistry mark

and a method:

- void Display() : Display student Name, class and the student’s average mark

2. Main program:

a. Display the menu:

1. Add a student
2. List students
3. Classify students
4. Exit

b. Create a List<Student> list to keep all students will be added

c. If user enters 1:

   i.Create a new Student object instance

   ii.Ask for Name, Class, Math, Physic, Chemistry …

   iii.Add the new student to the students list

d. If user enters 2:

   i.Loop in the students list and execute Display method

e. If user enters 3:

   i.Find all the students with average mark >=8.5 to a List<Student> list and named Goods
   Hint: Create a Predicate function IsGood

   ii.Find all the students with average mark >=7.0 and <8.5 to a List<Student> list and named Fairs
   Hint: Create a Predicate function IsFair

   iii.Find all the students with average mark >=5.0 and <7.0 to a List<Student> list and named Normals
   Hint: Create a Predicate function IsNormal

   iv.Find all the students with average mark <5.0 to a List<Student> list and named Bads
   Hint: Create a Predicate function IsBad

   v.Loop all elements in each Goods, Fairs, Normals, Bads list and execute student Display method

http://v1study.com/public/images/article/csharp-practical8.png

f. If user enters 4: Exit the program

 

Partial

Giả sử rằng có một tổ chức lớn trong đó bộ phận IT được chia thành hai cơ sở và Melbourne và Sydney. Nhiệm vụ bây giờ là phải thu thập dữ liệu của cả hai cơ sở. Các khách hàng của tổ chức Chức năng 

Giả sử ta có một lớp hoặc một structure rất lớn với rất nhiều thành phần được định nghĩa bên trong, lúc này ta cần phải tìm cách chia lớp hay cấu trúc đó thành những file nhỏ để tiện quản lý, nhưng phải đảm bảo sau khi chia nhỏ thì có thể lại kết hợp lại được khi muốn thực thi chương trình. Partial là cách thức mà C# cho phép ta chia nhỏ một lớp hay cấu trúc thành những phần nhỏ hơn.

Đặc điểm và ưu điểm của partial

Ta có thể áp dụng partial cho class, structure và interface. Dưới đây là một số đặc điểm cũng như ưu điểm của partial:

· Tách mã tạo khỏi mã ứng dụng.

· Dễ phát triển và bảo trì code hơn.

· Dễ gỡi lỗi (debug) hơn.

· Ngăn ngừa lập trình viên có thể vô tình sửa đổi mã lệnh.

csharp-partial-figure.png

Lưu ý: C# không hỗ trợ partial để định nghĩa kiểu liệt kê, tuy nhiên, ta có thể áp dụng partial cho kiểu generic.

Hợp nhất các thành phần trong quá trình biên dịch

Sau khi dùng partial để chia nhỏ class, structure hay interface thì ta có thể đặt chúng ở những vị trí khác nhau, và ta hoàn toàn có thể hợp nhất chúng tại thời điểm biên dịch chương trình. Những thành phần được chia nhỏ có thể gồm:

· Các chú thích XML

· Các Interface

· Các tham số kiểu generic

· Các biến lớp

· Các biến cục bộ

· Các phương thức

· Các Property

Partical Class

Ta có thể sử dụng partial để chia lớp thành những file nhỏ hơn, ví dụ ta muốn chia lớp thành hai file, một file chứa các thành phần private và một file chứa các thành phần public thì rõ ràng hiệu quả quản lý cũng như bảo mật sẽ tốt lên nhiều; hơn nữa điều này cũng cho phép các nhà phát triển cùng một lúc làm việc trên khác thành phần khác nhau của cùng một lớp, và sẽ giúp tăng tốc độ hoàn thành.

Ví dụ:

using System;
//Program: StudentDetails.cs
public partial class StudentDetails
{
  public void Display()
  {
    Console.WriteLine("Student Roll Number: " + _rollNo);
    Console.WriteLine("Student Name: " + _studName);
  }
}
//Program StudentDetails2.cs
public partial class StudentDetails
{
  int _rollNo;
  string _studName;
  public StudentDetails(int number, string name)
  {
    _rollNo = number;
    _studName = name;
  }
}
public class Students
{
  static void Main(string[] args)
  {
    StudentDetails objStudents = new StudentDetails(20, "Frank");
    objStudents.Display();
  }
}

Trong ví dụ trên, lớp StudentDetails được chia thành hai file là StudentDetails.cs và StudentDetails2.cs, trong đó StudentDetails.cs chứa phương thức Display(), còn StudentDetails2.cs chứa các biến lớp và một hàm tạo; lớp phương thức Main() tạo một thể hiện của lớp StudentDetails và gọi phương thức Display().

Output:

http://v1study.com/public/images/article/csharp-partial-class.png

Partial Method

Nullable

Trong C# thông thường chỉ những biến có kiểu tham chiếu mới có thể được gán giá trị null, tuy nhiên, C# cung cấp kiểu nullable để cho phép cả những biến có kiểu giá trị có thể được gán giá trị null. Biến có kiểu nullable sẽ được chỉ định hoặc là bằng cách sử dụng một giá trị đặc biệt hoặc là một biến bổ sung. Biến bổ sung này  có thể chỉ định giá trị null hoặc khác null cho biến yêu cầu; còn giá trị đặc biệt chỉ hữu ích khi giá trị đã sử dụng phải có tính nhất quán giữa các ứng dụng. Việc tạo và quản lý các trường bổ sung cho các biến này làm tốn bộ nhớ hơn và trở nên nhàm. Vần đề này có thể được giải quyết bằng cách sử dụng nullable.

Tạo kiểu nullable

Kiểu nullable là cách thức mà ta có thể định nghĩa giá trị null cho kiểu giá trị; điều này có nghĩa là một biến có kiểu giá trị có thể chứa giá trị null. Kiểu nullable là một thể hiện của structure System.Nullable<T>. Để tạo kiểu nullable ta có hai cách như sau:

Data_type? Variable_name = Value;

Hoặc:

System.Nullable<Data_type> Variable_name = Value;

Ví dụ:

int? n = null;
n = 123;
System.Nullable<float> f = null;
f = 456.0f;

Các đặc điểm của nullable

Biến có kiểu nullable có thể chứa giá trị null.

Biến có kiểu nullable làm việc như một biến thông thường.

Có thể trả lại giá trị được gán hoặc giá trị mặc định cho biến kiểu nullable.

Khi đem gán một biến kiểu nullable cho một biến không phải kiểu nullable thì cần phải sử dụng toán tử (??) để thực hiện theo cú pháp như sau:

Variable_name_non_nullable = Variable_name_nullable??Default_value;

Ví dụ:

int? n = null;
int m = 1000;
m = n??0; //m sẽ nhận giá trị 0 vì n chứa giá trị null

Lưu ý: Một trong những ứng dụng của kiểu nullable là để tích hợp C# với cơ sở dữ liệu trong đó các cột của bảng nào đó có thể chứa giá trị null. Nếu không có kiểu nullable thì không có cách nào để thể hiện chính xác những giá trị null trong các cột của bảng đó. Một ví dụ khác cũng để thấy được ý nghĩa của kiểu nullable là nếu ta có một biến kiểu bool thì tất nhiên biến đó sẽ chứa hoặc là giá trị true hoặc là giá trị false, những nếu ta lại muốn biến đó chứa giá trị không true mà cũng không false thì có chuyển kiểu của biến đó về kiểu bool?, khi đó biến có thể chứa thêm giá trị null.

Practical 9

USER MANAGEMENT SYSTEM

1. Create an interface called IUser in 2 files with these members:

IUserPro.cs:

a. Properties:

   int ID;                  => Index of the User, start from 1 to n
   string Name;        => User Name
   string Password;  => Password

b. Indexer:

   int? PhoneList

IUserMethod.cs:

   void Display()

2. Create a class name User

a. Implement interface IUser.

b. Indexer PhoneList implements a List<int?> type variable at the specified index. If the specified index is out of boudary, then return null.

c. The Display method will print Name, Password and all the phone numbers in PhoneList of User to console.

3. Build the menu:

1. Add User
2. Check User
3. Display all Users

4. If user chooses 1, then:

a. Create a new User object.

b. Set ID = Total users + 1.

c. Set Name = Read from console.

d. Set Password = Read from console.

e. Asks User to input number of phones and enter these phone to PhoneList.

f. Add the user to an Dictionary<int, IUser> with Key = UserID and Value = user.

5. If user chooses 2, then:

a. Ask user to enter ID.

b. Ask user to enter Name.

c. Ask user to enter Password.

d. Check in the Dictionary if the UserName and Password is match. If UserName and Password is match, show the message “User Checked” else show the message “User not found”.

6. If user chooses 3, then:

Display all Users and their phone numbers to the console using their Display method.

 

Practical 10

MOVIES MANAGEMENT SYSTEM

In this exam, you’ll have to create a Movies Management system. The system allows input, list, search … movies.

1. Create an interface name IMovie  contains these properties and methods:

a. Properties:

ID (int)

Name (string)

PublishDate (DateTime)

 Director (string)

 Subtitle(string)

 AverageRate (float) – Read only property

b. Methods:

void Display()

2. Create a class name Movie:

a. Implements the IMovie interface.

b. The method Display will print all Name, PublishDate, Director, Language and AverageRate of the movie  to the console.

c. Declare an array name RateList type double has size of 3 elements.

d. Create an indexer uses the array RateList in step 2c.

e. Create a method named Calculate to set AverageRate = average of 3 double elements in AverageList array.

3. Create a class name  CustomList  that inherits from interface IEnumerable has some properties and methods:

a. private ArrayList MovieList;

b. public void Add(IMovie movie);

c. public void Remove(IMovie movie); //remove movie from MovieList

d. public void Sort(IComparer comp); //sort the items in MovieList with Icomparer comp

e. class CustomList allows user to iterate each item in MovieList.

4. Create a class name CustomSort that allows user sort IMovie items  in MovieList of class CustomList by their AverageList.

5. Display  a tasks menu to choose:

PLEASE AN OPTION:

   1. Insert new Movie
   2. View list of Movie
   3. Sort Movie by Average List
   4. Delete a movie
   5. Exit

6. If user type 1 from keyboard then:

a. Create a new Movie  instance and input Name, PublishDate, Director, Language and then ask user to enter 3 rating points and set to the instance indexer:

- The ID is auto increament ( ID++ )

- Be sure to check format of PublishDate

b. Create a CustomList to keep the Movie instance in MovieList

7. If user type 2 from keyboard then:

Loop from all movie instances in the CustomList then executes the Display method and Calculate from Movie.

8. If user type 3 from keyboard then:

Sort all movie instances in the CustomList by  their AverageRate

9. While user not chooses Exit (type 5 from keyboard) then go back to the menu step 3 to ask user chooses an option.

Từ khóa

C# là ngôn ngữ sử dụng giới hạn những từ khóa. Phần lớn các từ khóa được sử dụng để mô tả thông tin. Chúng ta có thể nghĩ rằng một ngôn ngữ có nhiều từ khóa thì sẽ mạnh hơn. Điều này không chắc đúng, ít nhất là trong trường hợp ngôn ngữ C#, bởi C# có thể được sử dụng để làm bất cứ nhiệm vụ nào.

Bảng sau liệt kê các từ khóa của ngôn ngữ C#:

abstract

default

foreach

object

sizeof

unsafe

as

delegate

goto

operator

stackalloc

ushort

base

do

if

out

static

using

bool

double

implicit

override

string

virtual

break

else

in

params

struct

volatile

byte

enum

int

private

switch

void

case

event

interface

protected

this

while

catch

explicit

internal

public

throw

 

char

extern

is

readonly

true

 

checked

false

lock

ref

try

 

class

finally

long

return

typeof

 

const

fixed

namespace

sbyte

uint

 

continue

float

new

sealed

ulong

 

decimal

for

null

short

unchecked

 

Kiểu dữ liệu

C# là ngôn ngữ lập trình định kiểu mạnh, một ngôn ngữ mạnh về kiểu dữ liệu là phải khai báo kiểu của mỗi đối tượng khi tạo (kiểu số nguyên, số thực, kiểu chuỗi, kiểu điều khiển...) và trình biên dịch sẽ giúp cho người lập trình không bị lỗi khi chỉ cho phép một loại kiểu dữ liệu có thể được gán cho các kiểu dữ liệu khác. Kiểu dữ liệu của một đối tượng là một tín hiệu để trình biên dịch nhận biết kích thước của một đối tượng (kiểu int có kích thước là 4 byte) và khả năng của nó (như một đối tượng button có thể vẽ, phản ứng khi nhấn,...).

Tương tự như C++ hay Java, C# chia thành hai tập hợp kiểu dữ liệu chính: Kiểu xây dựng sẵn (built- in) mà ngôn ngữ cung cấp cho người lập trình và kiểu được người dùng định nghĩa (user-defined) do người lập trình tạo ra.

C# phân tập hợp kiểu dữ liệu này thành hai loại: Kiểu dữ liệu giá trị (value) và kiểu dữ  liệu tham chiếu (reference). Việc phân chi này do sự khác nhau khi lưu kiểu dữ liệu giá trị và kiểu dữ liệu tham chiếu trong bộ nhớ. Đối với một kiểu dữ liệu giá trị thì sẽ được lưu giữ kích thước thật trong bộ nhớ đã cấp phát là stack. Trong khi đó thì địa chỉ của kiểu dữ liệu tham chiếu thì được lưu trong stack nhưng đối tượng thật sự thì lưu trong bộ nhớ heap.

Nếu chúng ta có một đối tượng có kích thước rất lớn thì việc lưu giữ chúng trên bộ nhớ heap rất có ích, trong chương 4 sẽ trình bày những lợi ích và bất lợi khi làm việc với kiểu dữ liệu tham chiếu, còn trong chương này chỉ tập trung kiểu dữ kiểu cơ bản hay kiểu xây dựng sẵn.

@Ghi chú: Tất cả các kiểu dữ liệu xây dựng sẵn là kiểu dữ liệu giá trị ngoại trừ các đối tượng và chuỗi. Và tất cả các kiểu do người dùng định nghĩa ngoại trừ kiểu cấu trúc đều là kiểu dữ liệu tham chiếu.

Ngoài ra C# cũng hỗ trợ một kiểu con trỏ C++, nhưng hiếm khi được sử dụng, và chỉ khi nào làm việc với những đoạn mã lệnh không được quản lý (unmanaged code). Mã lệnh không được quản lý là các lệnh được viết bên ngoài nền .MS.NET, như là các đối tượng COM.

Kiểu dữ liệu xây dựng sẵn

Ngôn ngữ C# đưa ra các kiểu dữ liệu xây dựng sẵn rất hữu dụng, phù hợp với một ngôn ngữ lập trình hiện đại, mỗi kiểu dữ liệu được ánh xạ đến một kiểu dữ liệu được hỗ trợ bởi hệ thống xác nhận ngôn ngữ chung (Common Language Specification: CLS) trong MS.NET. Việc ánh xạ các kiểu dữ liệu nguyên thuỷ của C# đến các kiểu dữ liệu của .NET sẽ đảm bảo các đối tượng được tạo ra trong C# có thể được sử dụng đồng thời với các đối tượng được tạo bởi bất cứ ngôn ngữ khác được biên dịch bởi .NET, như VB.NET.

Mỗi kiểu dữ liệu có một sự xác nhận và kích thước không thay đổi, không giống như   C++, int trong C# luôn có kích thước là 4 byte bởi vì nó được ánh xạ từ kiểu Int32 trong . NET.

Bảng sau sẽ mô tả một số các kiểu dữ liệu được xây dựng sẵn:

Kiểu C#

Số byte

Kiểu .NET

Mô tả

byte

1

Byte

Số nguyên dương không dấu từ 0-255

char

2

Char

Ký tự Unicode

bool

1

Boolean

Giá trị logic true/ false

sbyte

1

Sbyte

Số nguyên có dấu ( từ -128 đến 127)

short

2

Int16

Số nguyên có dấu giá trị từ -32768 đến 32767.

ushort

2

Uịnt16

Số nguyên không dấu 0 – 65.535

int

4

Int32

Số    nguyên 2.147.483.647

dấu

–2.147.483.647

uint

4

Uint32

Số nguyên không dấu 0 – 4.294.967.295

float

4

Single

Kiểu dấu chấm động, giá trị xấp xỉ từ 3,4E- 38 đến 3,4E+38, với 7 chữ số có nghĩa..

double

8

Double

Kiểu dấu chấm động có độ chính xác gấp  đôi, giá trị xấp xỉ từ 1,7E-308 đến 1,7E+308, với 15,16 chữ số có nghĩa.

decimal

8

Decimal

Có độ chính xác đến 28 con số và giá trị thập phân, được dùng trong tính toán tài chính, kiểu này đòi hỏi phải có hậu tố “m” hay “M” theo sau giá trị.

long 8 Int64

Kiểu số nguyên có dấu có giá trị trong khoảng :

-9.223.370.036.854.775.808 đến

9.223.372.036.854.775.807

ulong 8 Uint64 Số nguyên không dấu từ 0 đến 0xffffffffffffffff

@Ghi chú: Kiểu giá trị logic chỉ có thể nhận được giá trị là true hay false mà thôi. Một giá trị nguyên không thể gán vào một biến kiểu logic trong C# và không có bất cứ chuyển đổi ngầm định nào. Điều này khác với C/C++, cho phép biến logic được gán giá trị nguyên, khi đó giá trị nguyên 0 là false và các giá trị còn lại là true.

Chọn kiểu dữ liệu

Thông thường để chọn một kiểu dữ liệu nguyên để sử dụng như short, int hay long thường dựa vào độ lớn của giá trị muốn sử dụng. Ví dụ, một biến ushort có thể lưu giữ giá trị từ 0 đến 65.535, trong khi biến ulong có thể lưu giữ giá trị từ 0 đến 4.294.967.295, do đó tùy vào miền giá trị của phạm vi sử dụng biến mà chọn các kiểu dữ liệu thích hợp nhất. Kiểu dữ liệu int thường được sử dụng nhiều nhất trong lập trình vì với kích thước 4 byte của nó cũng đủ để lưu các giá trị nguyên cần thiết.

Kiểu số nguyên có dấu thường được lựa chọn sử dụng nhiều nhất trong kiểu số trừ khi có lý do chính đáng để sử dụng kiểu dữ liệu không dấu.

Stack và Heap

Stack là một cấu trúc dữ liệu lưu trữ thông tin dạng xếp chồng tức là vào sau ra trước (Last In First Out : LIFO), điều này giống như chúng ta có một chồng các đĩa, ta cứ xếp các đĩa vào chồng và khi lấy ra thì đĩa nào nằm trên cùng sẽ được lập ra trước, tức là đĩa vào sau sẽ được lấy ra trước.

Trong C#, kiểu giá trị như kiểu số nguyên được cấp phát trên stack, đây là vùng nhớ được thiết lập để lưu các giá trị, và vùng nhớ này được tham chiếu bởi tên của biến.

Kiểu tham chiếu như các đối tượng thì được cấp phát trên heap. Khi một đối tượng được cấp phát trên heap thì địa chỉ của nó được trả về, và địa chỉ này được gán đến một tham chiếu.

Thỉnh thoảng cơ chế thu gom sẽ hũy đối tượng trong stack sau khi một vùng trong stack được đánh dấu là kết thúc. Thông thường một vùng trong stack được định nghĩa bởi một hàm. Do đó, nếu chúng ta khai báo một biến cục bộ trong một hàm là một đối tượng thì đối tượng này sẽ đánh dấu để hũy khi kết thúc hàm.

Những đối tượng trên heap sẽ được thu gom sau khi một tham chiếu cuối

cùng đến đối tượng đó được gọi.

Cách tốt nhất khi sử dụng biến không dấu là giá trị của biến luôn luôn dương, biến này  thường thể hiện một thuộc tính nào đó có miền giá trị dương. Ví dụ khi cần khai báo một biến lưu giữ tuổi của một người thì ta dùng kiểu byte (số nguyên từ 0-255) vì tuổi của người không thể nào âm được.

Kiểu float, double, và decimal đưa ra nhiều mức độ khác nhau về kích thước cũng như độ chính xác.Với thao tác trên các phân số nhỏ thì kiểu float là thích hợp nhất. Tuy nhiên lưu ý rằng trình biên dịch luôn luôn hiểu bất cứ một số thực nào cũng là một số kiểu double trừ khi chúng ta khai báo rõ ràng. Để gán một số kiểu float thì số phải có ký tự f theo sau.

float soFloat = 24f;

Kiểu dữ liệu ký tự thể hiện các ký tự Unicode, bao gồm các ký tự đơn giản, ký tự theo mã Unicode và các ký tự thoát khác được bao trong những dấu nháy đơn. Ví dụ, A là một ký tự đơn giản trong khi \u0041 là một ký tự Unicode. Ký tự thoát là những ký tự đặc biệt bao gồm hai ký tự liên tiếp trong đó ký tự dầu tiên là dấu chéo ‘\’. Ví dụ, \t là dấu tab. Bảng sau trình bày các ký tự đặc biệt.

Ký tự

Ý nghĩa

\’

Dấu nháy đơn

\”

Dấu nháy kép

\\

Dấu chéo

\0

Ký tự null

\a

Alert

\b Backspace
\f Sang trang form feed
\n Xuống dòng
\r Đầu dòng
\t Tab ngang
\v Tab dọc

Chuyển đổi các kiểu dữ liệu

Những đối tượng của một kiểu dữ liệu này có thể được chuyển sang những đối tượng của một kiểu dữ liệu khác thông qua cơ chế chuyển đổi tường minh hay ngầm định. Chuyển đổi nhầm định được thực hiện một cách tự động, trình biên dịch sẽ thực hiện công việc này. Còn chuyển đổi tường minh diễn ra khi chúng ta gán ép một giá trị cho kiểu dữ liệu khác.

Việc chuyển đổi giá trị ngầm định được thực hiện một cách tự động và đảm bảo là không mất thông tin. Ví dụ, chúng ta có thể gán ngầm định một số kiểu short (2 byte) vào một số kiểu int (4 byte) một cách ngầm định. Sau khi gán hoàn toàn không mất dữ liệu vì bất cứ giá trị nào của short cũng thuộc về int:

short x = 10;

int y = x;  // chuyển đổi ngầm định

Tuy nhiên, nếu chúng ta thực hiện chuyển đổi ngược lại, chắc chắn chúng ta sẽ bị mất thông tin. Nếu giá trị của số nguyên đó lớn hơn 32.767 thì nó sẽ bị cắt khi chuyển đổi. Trình biên dịch sẽ không thực hiện việc chuyển đổi ngầm định từ số kiểu int sang số kiểu short:

short x;

int y = 100;

x = y;   // Không biên dịch, lỗi !!!

Để không bị lỗi chúng ta phải dùng lệnh gán tường minh, đoạn mã trên được viết lại như sau:

short x;

int y = 500;

x = (short) y;   // Ép kiểu tường minh, trình biên dịch không báo lỗi

Biến và hằng

Biến

Một biến là một vùng lưu trữ với một kiểu dữ liệu. Trong ví dụ trước cả x, và y điều là biến. Biến có thể được gán giá trị và cũng có thể thay đổi giá trị khi thực hiện các lệnh trong chương trình.

Để tạo một biến chúng ta phải khai báo kiểu của biến và gán cho biến một tên duy nhất. Biến có thể được khởi tạo giá trị ngay khi được khai báo, hay nó cũng có thể được gán một giá trị mới vào bất cứ lúc nào trong chương trình. Ví dụ 1 sau minh họa sử dụng biến.

Ví dụ 1: Khởi tạo và gán giá trị đến một biến.

class MinhHoaC3

{

static void Main()

{

int bien1 = 9;

System.Console.WriteLine(“Sau khi khoi tao: bien1 ={0}”, bien1); bien1 = 15;

System.Console.WriteLine(“Sau khi gan: bien1 ={0}”, bien1);

}

}

Kết quả:

Sau khi khoi tao: bien1 = 9

Sau khi gan: bien1 = 15

-----------------------------------------------------------------------------

Ngay khi khai báo biến ta đã gán giá trị là 9 cho biến, khi xuất biến này thì biến có giá trị là 9. Thực hiện phép gán biến cho giá trị mới là 15 thì biến sẽ có giá trị là 15 và xuất kết quả là 15.

Gán giá trị xác định cho biến

C# đòi hỏi các biến phải được khởi tạo trước khi được sử dụng. Để kiểm tra luật này chúng ta thay đổi dòng lệnh khởi tạo biến bien1 trong ví dụ 3.1 như sau:

int  bien1;

và giữ nguyên phần còn lại ta được ví dụ 2:

Ví dụ 2: Sử dụng một biến không khởi tạo.

class MinhHoaC3

{

static void Main()

{

int bien1;

System.Console.WriteLine(“Sau khi khoi tao: bien1 ={0}”, bien1); bien1 = 15;

System.Console.WriteLine(“Sau khi gan: bien1 ={0}”, bien1);

}

}

Khi biên dịch đoạn chương trình trên thì trình biên dịch C# sẽ thông báo một lỗi sau:

...error CS0165: Use of unassigned local variable ‘bien1’

Việc sử dụng biến khi chưa được khởi tạo là không hợp lệ trong C#. Ví dụ 2 trên không hợp lệ.

Tuy nhiên không nhất thiết lúc nào chúng ta cũng phải khởi tạo biến. Nhưng để dùng được thì bắt buộc phải gán cho chúng một giá trị trước khi có một lệnh nào tham chiếu đến biến đó. Điều này được gọi là gán giá trị xác định cho biến và C# bắt buộc phải thực hiện điều này.

Ví dụ 3 minh họa một chương trình đúng.

Ví dụ 3.3: Biến không được khi tạo nhưng sau đó được gán giá trị.

class MinhHoaC3

{

static void Main()

{

int bien1; bien1 = 9;

System.Console.WriteLine(“Sau khi khoi tao: bien1 ={0}”, bien1); bien1 = 15;

System.Console.WriteLine(“Sau khi gan: bien1 ={0}”, bien1);

}

}

Hằng

Hằng cũng là một biến nhưng giá trị của hằng không thay đổi. Biến là công cụ rất mạnh, tuy nhiên khi làm việc với một giá trị được định nghĩa là không thay đổi, ta phải đảm bảo giá trị của nó không được thay đổi trong suốt chương trình. Ví dụ, khi lập một chương trình thí nghiệm hóa học liên quan đến nhiệt độ sôi, hay nhiệt độ đông của nước, chương trình cần  khai báo hai biến là DoSoi và DoDong, nhưng không cho phép giá trị của hai biến này bị thay đổi hay bị gán. Để ngăn ngừa việc gán giá trị khác, ta phải sử dụng biến kiểu hằng.

Hằng được phân thành ba loại: giá trị hằng (literal), biểu tượng hằng (symbolic constants), kiểu liệu kê (enumerations).

Giá trị hằng: ta có một câu lệnh gán như sau:

x = 100;

Giá trị 100 là giá trị hằng. Giá trị của 100 luôn là 100. Ta không thể gán giá trị khác cho 100 được.

Biểu tượng hằng: gán một tên cho một giá trị hằng, để tạo một biểu tượng hằng dùng từ khóa

const và cú pháp sau:

<const> <type> <tên hằng> = <giá trị>;

Một biểu tượng hằng phải được khởi tạo khi khai báo, và chỉ khởi tạo duy nhất một lần trong suốt chương trình và không được thay đổi. Ví dụ:

const int DoSoi = 100;

Trong khai báo trên, 32 là một hằng số và DoSoi là một biểu tượng hằng có kiểu nguyên. Ví dụ 4 minh họa việc sử dụng những biểu tượng hằng.

Vi dụ 4: Sử dụng biểu tượng hằng.

class MinhHoaC3

{

static void Main()

{

const int DoSoi = 100; // Độ C

const int DoDong = 0;  // Độ C

System.Console.WriteLine( “Do dong cua nuoc {0}”, DoDong ); System.Console.WriteLine( “Do soi cua nuoc {0}”, DoSoi );

}

}

Kết quả:

Do dong cua nuoc 0 Do soi cua nuoc 100

Ví dụ 4 tạo ra hai biểu tượng hằng chứa giá trị nguyên: DoSoi và DoDong, theo qui tắc đặt tên hằng thì tên hằng thường được đặt theo cú pháp Pascal, nhưng điều này không đòi hỏi bởi ngôn ngữ nên ta có thể đặt tùy ý.

Việc dùng biểu thức hằng này sẽ làm cho chương trình được viết tăng thêm phần ý nghĩa cùng với sự dễ hiểu. Thật sự chúng ta có thể dùng hằng số là 0 và 100 thay thế cho hai biểu tượng hằng trên, nhưng khi đó chương trình không được dễ hiểu và không được tự nhiên lắm. Trình biên dịch không bao giờ chấp nhận một lệnh gán giá trị mới cho một biểu tượng hằng. Ví dụ 3.4 trên có thể được viết lại như sau:

...

class MinhHoaC3

{

static void Main()

{

const int DoSoi = 100; // Độ C

const int DoDong = 0;  // Độ C

System.Console.WriteLine( “Do dong cua nuoc {0}”, DoDong );

System.Console.WriteLine( “Do soi cua nuoc {0}”, DoSoi ); DoSoi = 200;

}

}

Khi đó trình biên dịch sẽ phát sinh một lỗi sau:

error CS0131: The left-hand side of an assignment must be a variable, property or indexer.

Kiểu liệt kê (enum)

Kiểu liệt kê (enum) đơn giản là tập hợp các tên hằng có giá trị không thay đổi (thường được gọi là danh sách liệt kê).

Trong ví dụ 4 ở bài Biến và Hằng , có hai biểu tượng hằng có quan hệ với nhau:

const int DoDong = 0;

const int DoSoi = 100;

Do mục đích mở rộng ta mong muốn thêm một số hằng số   khác vào danh sách trên, như các hằng sau:

const int DoNong = 60;

const int DoAm = 40;

const int DoNguoi = 20;

Các biểu tượng hằng trên điều có ý nghĩa quan hệ với nhau, cùng nói về nhiệt độ của nước, khi khai báo từng hằng trên có vẻ cồng kềnh và không được liên kết chặt chẽ cho lắm. Thay vào đó C# cung cấp kiểu liệt kê để giải quyết vấn đề trên:

enum NhietDoNuoc

{

DoDong = 0,

DoNguoi = 20,

DoAm  = 40,

DoNong = 60,

DoSoi  = 100,

}

Mỗi kiểu liệt kê có một kiểu dữ liệu cơ sở, kiểu dữ liệu có thể là bất cứ kiểu dữ liệu nguyên nào như int, short, long... tuy nhiên kiểu dữ lịêu của liệt kê không chấp nhận kiểu ký tự. Để khai báo một kiểu liệt kê ta thực hiện theo cú pháp sau:

[thuộc tính] [bổ sung] enum <tên liệt kê> [:kiểu cơ sở] {danh sách các thành phần liệt kê};

Thành phần thuộc tính và bổ sung là tự chọn sẽ được trình bày trong phần sau của sách.

Trong phần này chúng ta sẽ tập trung vào phần còn lại của khai báo. Một kiểu liệt kê bắt đầu với từ khóa enum, tiếp sau là một định danh cho kiểu liệt kê:

enum NhietDoNuoc

Thành phần kiểu cơ sở chính là kiểu khai báo cho các mục trong kiểu liệt kê. Nếu bỏ qua thành phần này thì trình biên dịch sẽ gán giá trị mặc định là kiểu nguyên int, tuy nhiên chúng ta có thể sử dụng bất cứ kiểu nguyên nào như ushort hay long,..ngoại trừ kiểu ký tự. Đoạn ví dụ sau khai báo một kiểu liệt kê sử dụng kiểu cơ sở là số nguyên không dấu uint:

enum KichThuoc :uint

{

Nho = 1,

Vua = 2,

Lon = 3,

}

Lưu ý là khai báo một kiểu liệt kê phải kết thúc bằng một danh sách liệt kê, danh sách liệt kê này phải có các hằng được gán, và mỗi thành phần phải phân cách nhau dấu phẩy.

Ví dụ: Sử dụng kiểu liệt kê để đơn giản chương trình.

class MinhHoaC3

{

// Khai báo kiểu liệt kê enum NhietDoNuoc

{

DoDong = 0,

DoNguoi = 20,

DoAm  = 40,

DoNong = 60,

DoSoi  = 100,

}

static void Main()

{

System.Console.WriteLine( “Nhiet do dong: {0}”, NhietDoNuoc.DoDong);

System.Console.WriteLine( “Nhiet do nguoi: {0}”, NhietDoNuoc.DoNguoi);

System.Console.WriteLine( “Nhiet do am: {0}”, NhietDoNuoc.DoAm);

System.Console.WriteLine( “Nhiet do nong: {0}”, NhietDoNuoc.DoNong);

System.Console.WriteLine( “Nhiet do soi: {0}”, NhietDoNuoc.DoSoi);

}

}

Kết quả:

Nhiet do dong: 0

Nhiet do nguoi: 20

Nhiet do am: 40

Nhiet do nong: 60

Nhiet do soi: 100

-----------------------------------------------------------------------------

Mỗi thành phần trong kiểu liệt kê tương ứng với một giá trị số, trong trường hợp này là một  số nguyên. Nếu chúng ta không khởi tạo cho các thành phần này thì chúng sẽ nhận các giá trị tiếp theo với thành phần đầu tiên là 0.

Ta xem thử khai báo sau:

enum Thutu

{

ThuNhat,

ThuHai,

ThuBa = 10,

ThuTu

}

Khi đó giá trị của ThuNhat là 0, giá trị của ThuHai là 1, giá trị của ThuBa là 10 và giá trị  của ThuTu là 11.

Kiểu liệt kê là một kiểu hình thức do đó bắt buộc phải thực hiện phép chuyển đổi tường minh với các kiêu giá trị nguyên:

int x = (int) ThuTu.ThuNhat;

Solution Practical 2 - Exrcise 1

Tập tin Product.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Solution_Ex1_Prac2
{
    class Product
    {
        public String Name;
        public String Description;
        public double Price;
        public int[] Rate;
        public void viewInfo()
        {
            Console.WriteLine("Name: "+Name);
            Console.WriteLine("Price: "+Price);
            Console.WriteLine("Description: "+Description);
        }
    }
}

Tập tin Shop.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace Solution_Ex1_Prac2
{
    class Shop
    {
        ArrayList ProductList = new ArrayList();
        public void addProduct(Product p)
        {
            Console.Write("Name: ");
            p.Name = Console.ReadLine();
            do{
            Console.Write("Price: ");            
            p.Price = double.Parse(Console.ReadLine());
            }while(!(p.Price>0 && p.Price<=100));
            Console.Write("Description: ");
            p.Description = Console.ReadLine();
            Console.Write("Number of rates: ");
            int N = int.Parse(Console.ReadLine());
            p.Rate = new int[N];
            for (int i = 0; i < N; i++)
            {
                do{
                Console.Write("Rate "+(i+1)+": ");
                p.Rate[i] = int.Parse(Console.ReadLine());
                }while(!(p.Rate[i]>=1 && p.Rate[i]<=5));
            }
            ProductList.Add(p);
        }
        public void removeProduct()
        {
            bool search = false;
            Console.Write("Enter the name of the product you want to delete: ");
            String name = Console.ReadLine();
            foreach (Product p in ProductList)
            {
                if (name.Equals(p.Name))
                {
                    search = true;
                    ProductList.Remove(p);
                    break;
                }
            }
            if (search == true)
                Console.WriteLine("Remove Sucessful!");
            else
                Console.WriteLine("Not Found!");
        }
        public void iterateProductList()
        {
            foreach (Product p in ProductList)
            {
                p.viewInfo();
                float averateRate = 0;
                foreach (int rate in p.Rate)
                {
                    averateRate += rate;
                }
                Console.WriteLine("Averate Rate: "+averateRate/p.Rate.Length);
            }
        }
        public void searchProduct()
        {
            double minPrice;
            double maxPrice;
            bool search = false;
            do{
            Console.Write("Please enter Min price: ");
            minPrice = double.Parse(Console.ReadLine());
            }while(!(minPrice>0 && minPrice<=100));
            do
            {
                Console.Write("Please enter Max price: ");
                maxPrice = double.Parse(Console.ReadLine());
            } while (!(minPrice <= 100 && maxPrice>minPrice));
            foreach (Product p in ProductList)
            {
                if (p.Price >= minPrice && p.Price <= maxPrice)
                {
                    search = true;
                    p.viewInfo();
                }
            }
            if (search == false)
            {
                Console.WriteLine("Not Found!");
            }
        }
    }
}

Tập tin điều khiển Program.cs (chứa hàm Main()):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Solution_Ex1_Prac2
{
    class Program
    {
        static void Menu()
        {
            Console.WriteLine("1. Add new Product");
            Console.WriteLine("2. Remove Product");
            Console.WriteLine("3. Iterate product list");
            Console.WriteLine("4. Search product");
            Console.WriteLine("5. Exit");
        }
        static void Main(string[] args)
        {
            int choice;
            Shop shop = new Shop();
            Menu();
            while (true)
            {
                Console.Write("Please choose an item: ");
                choice = int.Parse(Console.ReadLine());
                switch (choice)
                {
                    case 1:
                        Product p = new Product();
                        shop.addProduct(p);
                        break;
                    case 2:
                        shop.removeProduct();
                        break;
                    case 3:
                        shop.iterateProductList();
                        break;
                    case 4:
                        shop.searchProduct();
                        break;
                    case 5: return;
                }
            }
        }
    }
}

Solution Practical3_Exercise1

Xin xem bài tập tại ĐÂY.

Các Interface:

using System;
using System.Collections.Generic;
using System.Text;

namespace Practical3_Exercise3
{
    interface IAnimal
    {
        int ID
        {
            get;
            set;
        }
        string Name
        {
            get;
            set;
        }
        int Age
        {
            get;
            set;
        }
    }
    interface ITerrestrialAnimal :IAnimal
    {
         void Move();
    }
    interface IMarineAnimal : IAnimal
    {
         void Move();
    }
}

Lớp Cat:

using System;
using System.Collections.Generic;
using System.Text;

namespace Practical3_Exercise3
{
    class Cat:ITerrestrialAnimal
    {
        
        int id;
        string name;
        int age;
        public int ID
        {
            set
            {
                id = value;
            }
            get
            {
                return id;
            }
        }
        public string Name
        {
            set
            {
                name = value;
            }
            get
            {
                return name;
            }
        }

        public int Age
        {
            set
            {
                age = value;
            }
            get
            {
                return age;
            }
        }
        
        public void Move()
        {
            Console.WriteLine("Run");
        }
        public override string ToString()
        {                     
            Console.WriteLine(GetType());
            Console.WriteLine("Id:" + ID);
            Console.WriteLine("Name:" + Name);
            Console.WriteLine("Age:" + Age);
            return "";
        }
    }
}

Lớp Fish:

using System;
using System.Collections.Generic;
using System.Text;

namespace Practical3_Exercise3
{
    class Fish :IMarineAnimal
    {
        int id;
        string name;
        int age;

        public int ID
        {
            set
            {
                id = value;
            }
            get
            {
                return id;
            }
        }
        public string Name
        { 
            set 
            {
                name = value;
            }
            get
            {
                return name;
            }
        }

        public int Age
        {
            set
            {
                age = value;
            }
            get
            {
                return age;
            }
        }
        public void Move()
        {
            Console.WriteLine("Swim");
        }
        public override string ToString()
        {
            Console.WriteLine(GetType());
            Console.WriteLine("Id: " + ID);
            Console.WriteLine("Name: " + Name);
            Console.WriteLine("Age: " + Age);
            return "";
        }
    }
}

Lớp Crocodile:

using System;
using System.Collections.Generic;
using System.Text;

namespace Practical3_Exercise3
{
    class Crocodile :ITerrestrialAnimal,IMarineAnimal
    {
        int id;
        string name;
        int age;
        public int ID
        {
            set
            {
                id = value;
            }
            get
            {
                return id;
            }
        }

        public string Name
        {
            set
            {
                name = value;
            }
            get
            {
                return name;
            }
        }

        public int Age
        {
            set
            {
                age = value;
            }
            get
            {
                return age;
            }
        }

        void ITerrestrialAnimal.Move()
        {
            Console.WriteLine("Run");
        }

         void IMarineAnimal.Move()
        {
            Console.WriteLine("Swim");
        }

        public override string ToString()
        {
            Console.WriteLine(GetType());
            Console.WriteLine("Id:"+ID);
            Console.WriteLine("Name:" +Name);
            Console.WriteLine("Age:" +Age);
            return "";
        }
    }
}

Lớp Test:

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;

namespace Practical3_Exercise3
{
    class Test
    {
        
        int count=0;
        Hashtable ListAnimal = new Hashtable();

        public void AddCat()
        {
            Cat obj = new Cat();
            obj.ID = count;            
            Console.Write("Name of cat:");
            obj.Name = Console.ReadLine();
            Console.Write("Age of cat:");
            obj.Age=int.Parse(Console.ReadLine());
            ListAnimal.Add(count, obj);
            count++;
        }

        public void AddFish()
        {
            Fish obj = new Fish();
            obj.ID = count;         
            Console.Write("Name of fish:");
            obj.Name = Console.ReadLine();
            Console.Write("Age of fish:");
            obj.Age = int.Parse(Console.ReadLine());
            ListAnimal.Add(count, obj);
            count++;
        }

        public void AddCrocodile()
        {
            Crocodile obj = new Crocodile();
            obj.ID = count;            
            Console.Write("Name of crocodile: ");
            obj.Name = Console.ReadLine();
            Console.Write("Age of crocodile: ");
            obj.Age = int.Parse(Console.ReadLine());            
            ListAnimal.Add(count, obj);
            count++;
        }

        public void ViewTerrestrialAnimals()
        {
            Cat c = new Cat();
            for (int i = 0; i < ListAnimal.Count; i++)
                if (ListAnimal[i].GetType().Equals(c.GetType()))
                {
                    ListAnimal[i].ToString();
                    c.Move();
                }
        }

        public void ViewMarineAnimals()
        {
            Fish f = new Fish();
            for (int i = 0; i < ListAnimal.Count; i++)
                if (ListAnimal[i].GetType().Equals(f.GetType()))
                {
                    ListAnimal[i].ToString();
                    f.Move();
                }
        }

        public void ViewAllAnimals()
        {
            foreach(DictionaryEntry de in ListAnimal.Values)
                Console.WriteLine(de);
        }

        public void DeleteAnimals()
        {
            int n;
            Console.Write("Please select a key: ");
            int.TryParse(Console.ReadLine(), out n);
            foreach(DictionaryEntry de in ListAnimal)
                if (de.Key.Equals(n))
                {
                    ListAnimal.Remove(n);
                    break;
                }
            Console.WriteLine(ListAnimal.Count);
        }

        public static void Main()
        {
            Test t = new Test();
            Cat c = new Cat();     
            int choice;
            Console.WriteLine("1.Create a Crocodile");
            Console.WriteLine("2.Create a Cat");
            Console.WriteLine("3.Create a Fish");
            Console.WriteLine("4.View Terrestrial Animals");
            Console.WriteLine("5.View Marine Animals");
            Console.WriteLine("6.View All Animals");
            Console.WriteLine("7.Delete Animal");
            Console.WriteLine("8.Exit");
            do
            {
                Console.Write("\nPlease select an item: ");
                int.TryParse(Console.ReadLine(), out choice);
                switch (choice)
                {
                    case 1: t.AddCrocodile();
                        break;
                    case 2: t.AddCat();
                        break;
                    case 3: t.AddFish();
                        break;
                    case 4: t.ViewTerrestrialAnimals();
                        break;
                    case 5: t.ViewMarineAnimals();
                        break;
                    case 6: t.ViewAllAnimals();
                        break;
                    case 7: t.DeleteAnimals();
                        break;
                    case 8: return;
                }
            } while (choice != 8);
        }
    }
}

Solution Practical 3 - Exrcise 2

Xin xem bài tập tại ĐÂY.

File INews.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Solution_Practical3_Exercise2
{
    interface INews
    {
        int ID { get; set; }
        string Title { get; set; }
        DateTime PublishDate { get; set; }
        string Author { get; set; }
        string Content { get; set; }
        float AverageRate { get; }
        void Display();
    }
}

File News.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Solution_Practical3_Exercise2
{
    class News:INews
    {
        int id;
        public int ID
        {
            get
            {
                return id;
            }
            set
            {
                id = value;
            }
        }
        string title;
        public string Title
        {
            get
            {
                return title;
            }
            set
            {
                title = value;
            }
        }
        DateTime publishdate;
        public DateTime PublishDate
        {
            get
            {
                return publishdate;
            }
            set
            {
                publishdate = value;
            }
        }
        string author;
        public string Author
        {
            get
            {
                return author;
            }
            set
            {
                author = value;
            }
        }
        string content;
        public string Content
        {
            get
            {
                return content;
            }
            set
            {
                content = value;
            }
        }
        float averagerate;
        public float AverageRate
        {
            get { return averagerate; }
        }

        public void Display()
        {
            Console.WriteLine("Title:"+Title);
            Console.WriteLine("Publish date: "+PublishDate);
            Console.WriteLine("Author: "+Author);
            Console.WriteLine("Content: "+Content);
            Console.WriteLine("Averate rate: "+AverageRate);
        }
        int[] RateList = new int[3];
        public int this[int index] //Indexer
        {
            get
            {
                return RateList[index];
            }
            set
            {
                RateList[index] = value;
            }
        }
        public void Calculate(){
            averagerate= (float)(RateList[0]+RateList[1]+RateList[2])/3;
        }
    }
}

File Program.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace Solution_Practical3_Exercise2
{
    class Program
    {
        static Hashtable NewsList = new Hashtable();
        static int count = 0;
        static void Menu()
        {
            Console.WriteLine("1. Insert news");
            Console.WriteLine("2. View list news");
            Console.WriteLine("3. Averate rate");
            Console.WriteLine("4. Exit");
        }
        static void InsertNews()
        {
            News news = new News();
            news.ID = ++count;
            Console.Write("Title: ");
            news.Title = Console.ReadLine();
            while (true)
            {
                try
                {
                    Console.Write("Publish date: ");
                    news.PublishDate = DateTime.Parse(Console.ReadLine());
                    break;
                }
                catch (Exception e)
                {
                    Console.WriteLine("Publish date is not formatted!");
                }
            }
            Console.Write("Author: ");
            news.Author = Console.ReadLine();
            Console.Write("Content: ");
            news.Content = Console.ReadLine();
            for (int i = 0; i < 3; i++)
            {
                Console.Write("Rate "+(i+1)+": ");
                news[i] = int.Parse(Console.ReadLine());
            }
            NewsList.Add(news.ID, news);
        }
        static void ViewNews()
        {
            if (NewsList.Count > 0)
            {
                foreach (News news in NewsList.Values)
                {
                    news.Display();
                }
            }
            else
            {
                Console.WriteLine("News not found!");
            }
        }
        static void EverateRate()
        {
            if (NewsList.Count > 0)
            {
                foreach (News news in NewsList.Values)
                {
                    news.Calculate();
                    news.Display();
                }
            }
            else
            {
                Console.WriteLine("News not found!");
            }
        }
        static void Main(string[] args)
        {
            int choice;
            Menu();
            while (true)
            {
                Console.Write("Please select an item: ");
                choice = int.Parse(Console.ReadLine());
                switch (choice)
                {
                    case 1: InsertNews(); break;
                    case 2: ViewNews(); break;
                    case 3: EverateRate(); break;
                    case 4: return;
                }
            }
        }
    }
}