Toán tử as kết hợp toán tử is và phép gán bằng cách đầu tiên kiểm tra hợp lệ phép gán (kiểm tra toán tử is trả về true) rồi sau đó phép gán được thực hiện. Nếu phép gán không hợp lệ (khi phép gán trả về giá trị false), thì toán tử as trả về giá trị null.
Ghi chú: Từ khóa null thể hiện một tham chiếu không tham chiếu đến đâu cả (null reference). Đối tượng có giá trị null tức là không tham chiếu đến bất kỳ đối tượng nào.
Sử dụng toán tử as để loại bỏ việc thực hiện các xử lý ngoại lệ. Đồng thời cũng né tránh việc thực hiện kiểm tra dư thừa hai lần. Do vậy, việc sử dụng tối ưu của phép gán cho giao diện là sử dụng as.
Cú pháp sử dụng toán tử as như sau: <biểu thức> as <kiểu dữ liệu>
Đoạn chương trình sau thay thế việc sử dụng toán tử is bằng toán tử as và sau đó thực hiện việc kiểm tra xem giao diện được gán có null hay không:
static void Main() {
IHinh hinh = h as IHinh; if (h != null)
{
hinh.InThongTin();
Console.WriteLine("Hinh thuc thi IHinh"); }
else
h.InThongTin(); Console.Read(); }
Ta có thể so sánh đoạn mã IL sau với đoạn mã IL sử dụng toán tử is trước sẽ thấy đoạn mã sau có nhiều hiệu quả hơn:
IL_0023: isinst IHinh
IL_0028: stloc.2
IL_0029: ldloc.2
IL_002a: brfalse.s IL_0034
IL_002c: ldloc.2
IL_002d: callvirt instance void IHinh::InThongTin()
§Ghi chú: Nếu mục đích của chúng ta là kiểm tra một đối tượng có hỗ trợ một giao diện và sau đó là thực hiện việc gán cho một giao diện, thì cách tốt nhất là sử dụng toán tử as là hiệu quả nhất. Tuy nhiên, nếu chúng ta chỉ muốn kiểm tra kiểu dữ liệu và không thực hiện phép gán ngay lúc đó. Có lẽ chúng ta chỉ muốn thực hiện việc kiểm tra nhưng không thực hiện việc gán, đơn giản là chúng ta muốn thêm nó vào danh sách nếu chúng thực sự là một giao diện. Trong trường hợp này, sử dụng toán tử
is là cách lựa chọn tốt nhất.
21.2. Thực thi giao diện 21.2.1. Thực thi giao diện
Khi thực thi một lớp chúng ta có thể tự do đánh dấu bất kỳ hay tất cả các phương thức thực thi giao diện như là một phương thức ảo. Ví dụ, lớp HinhChuNhat thực thi giao diện IHinh và có thể đánh dấu InThongTin() như là phương thức ảo.
Những người phát triển sau có thể dẫn xuất một kiểu dữ liệu mới từ lớp HinhTron, có thể là lớp HinhVuong, và những người này mong muốn lớp HinhVuong thực thi phương thức InThongTin() riêng của lớp hình vuông.
Ví dụ 4.3
using System;
namespace LTHDTC {
//Xây dựng giao diện IHinh interface IHinh { string TenHinh { get; } double ChuVi(); double DienTich(); void InThongTin(); }
//Lớp HinhChuNhat triển khai giao diện IHinh class HinhChuNhat : IHinh
{
protected double chieuDai, chieuRong; // Thực thi phương thức của giao diện
public HinhChuNhat(double chieuDai, double chieuRong) {
this.chieuDai = chieuDai; this.chieuRong = chieuRong; }
// Thực thi phương thức của giao diện và khai báo phương thức là phương //thức ảo, để các lớp kế thừa lớp có thể tự do thực thi phương thức của riêng mình/
public virtual void InThongTin() {
Console.WriteLine("{0} co dien tich la: {1}; chu vi la: {2}", TenHinh, DienTich(), ChuVi());
}
public double ChuVi() {
return 2 * (chieuRong + chieuDai); }
public double DienTich() {
return chieuDai * chieuRong; }
public virtual string TenHinh {
get { return "Hinh chu nhat"; } }
}
// Lớp HinhVuong kế thừa từ HinhChuNhat có thể ghi đè các phương thức ảo của //lớp cơ sở
class HinhVuong : HinhChuNhat {
public HinhVuong(double canh) : base(canh, canh)
{ }
public override string TenHinh {
get {
return "Hinh Vuong"; }
}
// Ghi đè phương thức ảo của lớp cơ sở public override void InThongTin() { base.InThongTin();
} }
class Chay {
static void Main() {
IHinh hv = new HinhVuong(4) as IHinh; if (hv != null) { hv.InThongTin(); } Console.Read(); } } }
Do lớp HinhVuong kế thừa từ HinhChuNhat, mà HinhChuNhat lại triển khai giao diện Ihinh, nên để thực thi phương thức InThongTin ta có 4 cách, đó là những cách nào?
+ Trực tiếp từ đối tượng HinhVuong:
HinhVuong hv = new HinhVuong(4);
hv.InThongTin();
+ Thông qua đối tượng HinhChuNhat:
HinhChuNhat hv = (HinhChuNhat)new HinhVuong(4);
hv.InThongTin();
+ Thông qua giao diện mà lớp cơ sở của nó triển khai
HinhVuong hv = new HinhVuong(4);
IHinh ihv = hv as IHinh;
ihv.InThongTin();
+ Thông qua giao diện mà lớp cơ sở của nó triển khai
HinhChuNhat h;
HinhVuong hv = new HinhVuong(4);
IHinh ihv = h as IHinh;
ihv.InThongTin();
Thực thi giao diện tường minh
Trong việc thực thi giao diện cho tới giờ, những lớp thực thi tạo ra các phương thức thành viên cùng ký hiệu và kiểu trả về như là phương thức được mô tả trong giao diện. Chúng ta không cần thiết khai báo tường minh rằng đây là một thực thi của một giao diện, việc này được hiểu ngầm bởi trình biên dịch.
Tuy nhiên, có vấn đề xảy ra khi một lớp thực thi hai giao diện và cả hai giao diện này có các phương thức cùng một ký hiệu. Ví dụ 4.4 tạo ra hai giao diện: IHinh và IDaGiac. Sau đó thực thi phương thức InThongTin() trong giao diện IDiem phương thức InThongTin() để in thông tin về đa giác. Không may là phương thức này sẽ tranh chấp với phương thức InThongTin() của IHinh mà phải thực thi.
Bởi vì cả hai giao diện cùng có một phương thức InThongTin,việc thực thi lớp HinhChuNhat phải sử dụng thực thi tường minh cho mỗi phương thức. Với việc thực thi tường minh, lớp thực thi HinhChuNhat sẽ khai báo tường minh cho mỗi phương thức:
void IHinh.InThongTin ();
Điều này sẽ giải quyết việc tranh chấp, nhưng nó sẽ tạo ra hàng loạt các hiệu ứng thú vị.
+ Với các phương thức khác thì không cần thiết phải sử dụng thực thi tường minh vì không có sự tranh chấp cho nên ta khai báo như thông thường.
+ Phương thức thực thi tường minh không có bổ sung truy cập:
void IHinh.InThongTin (); phương thức này được hiểu ngầm định là public
Thật vậy, một phương thức được khai báo tường minh thì sẽ không được khai báo với các từ khóa bổ sung truy cập: abstract, virtual, override, và new.
+ Chúng ta không thể truy cập phương thức thực thi tường minh thông qua chính đối tượng,
h.InThongTin();
Trình biên dịch sẽ báo lỗi
Error 1 'LTHDTC.HinhChuNhat' does not contain a definition for 'InThongTin' and no extension method 'InThongTin' accepting a first argument of type
'LTHDTC.HinhChuNhat' could be found (are you missing a using directive or an assembly reference?) C:\Tai lieu giang day\Lap trinh huong doi tuong\Vi
du\LTHDTC\LTHDTC\clsHinh.cs 171 15 LTHDTC
Khi đó muốn gọi phương thức InThongTin thì ta phải gán biến đối tượng HinhChuNhat cho một đối tượng của giao
static void Main() {
HinhChuNhat h = new HinhChuNhat(3, 4); h.InThongTin(); IDaGiac d = h as IDaGiac; if (d != null) { d.InThongTin(); } Console.Read(); }
Sử dụng thực thi tường minh được áp dụng trong ví dụ 4.4
Ví dụ 4.4: Thực thi tường minh.
using System; namespace LTHDTC { interface IDaGiac { void InThongTin(); } interface IHinh
{ string TenHinh { get; } double ChuVi(); double DienTich(); void InThongTin(); }
class HinhVuong : HinhChuNhat {
public HinhVuong(double canh) : base(canh, canh)
{ }
public override string TenHinh {
get {
return "Hinh Vuong"; }
}
public void InThongTin() {
Console.WriteLine("Day la lop hinh vuong da ghi de"); }
}
class HinhChuNhat : IHinh, IDaGiac {
protected double chieuDai, chieuRong;
public HinhChuNhat(double chieuDai, double chieuRong) {
this.chieuDai = chieuDai; this.chieuRong = chieuRong; }
// Phương thức thực thi tường minh của giao diện IHinh void IHinh.InThongTin ()
{
Console.WriteLine("{0} co dien tich la: {1}; chu vi la: {2}", TenHinh, DienTich(), ChuVi());
}
// Phương thức thực thi tường minh của giao diện IDaGiac void IDaGiac.InThongTin()
{
Console.WriteLine("{0} là tứ giác có các góc bằng 90 độ",TenHinh ); }
public double ChuVi() {
return 2 * (chieuRong + chieuDai); }
public double DienTich() {
return chieuDai * chieuRong; }
public virtual string TenHinh {
get { return "Hinh chu nhat"; } }
} }
class Chay {
static void Main() {
h.InThongTin(); IDaGiac d = h as IDaGiac; if (d != null) { d.InThongTin(); } Console.Read(); } } }
Lựa chọn việc thể hiện phương thức giao diện
Những người thiết kế lớp có thể thu được lợi khi một giao diện được thực thi thông qua thực thi tường minh và không cho phép các thành phần client của lớp truy cập trừ phi sử dụng thông qua việc gán cho giao diện.
Giả sử nghĩa của đối tượng HinhChuNhat chỉ ra rằng nó thực thi giao diện IHinh, nhưng không muốn phương thức InThongTin() là phần giao diện public
của lớp HinhChuNhat. Chúng ta có thể sử dụng thực thi tường minh để chắc chắn chỉ có thể truy cập thông qua việc gán cho giao diện. Điều này cho phép chúng ta lưu trữ ngữ nghĩa của lớp HinhChuNhat trong khi vẫn có thể thực thi được giao diện IHinh. Nếu thành phần client muốn đối tượng thực thi giao diện IHinh, nó có thể thực hiện gán tường minh cho giao diện để gọi các phương thức thực thi giao diện. Nhưng khi sử dụng đối tượng HinhChuNhat thì nghĩa là không có phương thức InThongTin().
Thật vậy, chúng ta có thể lựa chọn thể hiện những phương thức thông qua thực thi tường minh, do đó chúng ta có thể trưng bày một vài phương thức thực thi như là một phần của lớp HinhChuNhat và một số phương thức khác thì không. Trong ví dụ 4.4, đối tượng HinhChuNhat trưng bày phương thức ChuVi(), DienTich() như là phương thức của lớp HinhChuNhat, nhưng phương thức InThongTin() chỉ được thể hiện thông qua gán cho giao diện. Thậm chí nếu IHinh không có phương thức InThongTin(), chúng ta cũng có thể chọn thực thi tường minh phương thức InThongTin() để phương thức không được thể hiện ra bên ngoài như các phương thức của HinhChuNhat.
Chúng ta lưu ý rằng vì thực thi giao diện tường minh ngăn ngừa việc sử dụng từ khóa virtual, một lớp dẫn xuất có thể được hỗ trợ để thực thi lại phương thức. Do đó, nếu HinhVuong dẫn xuất từ HinhChuNhat, nó có thể được thực thi lại phương thức HinhChuNhat.InThongTin () bởi vì lớp HinhChuNhat thực thi phương thức InThongTin() không phải ảo.
Ẩ n thành viên
Ngôn ngữ C# cho phép ẩn các thành viên của giao diện. Ví dụ, chúng ta có một giao diện
IBase với một thuộc tính P:
interface IBase
{
int P { get; set;}
}
và sau đó chúng ta dẫn xuất từ giao diện này ra một giao diện khác, IDerived, giao diện mới này làm ẩn thuộc tính P với một phương thức mới P():
interface IDerived : IBase
{
new int P();
}
Việc cài đặt này là một ý tưởng tốt, bây giờ chúng ta có thể ẩn thuộc tính P trong lớp cơ sở. Một thực thi của giao diện dẫn xuất này đòi hỏi tối thiểu một thành viên giao diện tường minh. Chúng ta có thể sử dụng thực thi tường minh cho thuộc tính của lớp cơ sở hoặc của phương thức dẫn xuất, hoặc chúng ta có thể sử dụng thực thi tường minh cho cả hai. Do đó, ba phiên bản được viết sau đều hợp lệ:
class myClass : IDerived
{
int IBase.p { get{...}}
// thực thi ngầm định phương thức dẫn xuất
public int P() {...}
}
class myClass : IDerived
{
// thực thi ngầm định cho thuộc tính cơ sở
public int P { get{...}}
// thực thi tường minh phương thức dẫn xuất
int IDerived.P() {...}
}
class myClass : IDerived
{
// thực thi tường minh cho thuộc tính cơ sở
int IBase.P { get{...}}
// thực thi tường minh phương thức dẫn xuất
int IDerived.P(){...}
}