Reflection
Reflection là 1 thuật ngữ bao phủ 1 lớp cơ sở khác của .NET mà cho phép ta tìm ra thông
tin về các kiểu trong chương trình. hầu hết những lớp này nằm trong namespace
System.Reflection, và có 1 số lớn các lớp khác trong namespace này.
trong phần này ta sẽ tìm hiểu lớp system.Type , mà cho phép ta truy nhập thông tin liên
quan đến việc định nghĩa bất kì kiểu dữ liệu nào được cho.tiếp theo ta sẽ tìm hiểu lớp
System.Reflection.assembly mà cho phép ta truy xuất thông tin về assembly được cho,
hoặc tải assembly đó vào trong chương trình của ta.cuố
i cùng ta sẽ xét ví dụ
WhatsNewAttributes
Lớp System.Type
Ta đã dùng lớp Type 1 số lần để lấy tên của 1 kiểu :
Type t = typeof(double)
Mặc dù ta cho rằng type là 1 lớp nhưng thực sự nó là 1 lớp cơ sở trừu tượng, bất cứ khi
nào ta khởi tạo 1 đối tượng type ta thực sự khởi tạo 1 lớp dẫn xuất của type.type có 1 lớp
dẫn xuất đáp ứng mỗi kiểu dữ liệu.có 3 cách lấy 1 tham chiếu Type mà chuyển cho kiểu
dữ liệu bất kì:
- Dùng tác tử typeof. tác tử này lấy tên của kiểu như là thông số
.
- Dùng phương thức Gettype() , mà tất cả các lớp kế thừa từ System.Object:
double d = 10;Type t = d.GetType();
Gettype() hữu ích khi ta có 1 tham chiếu đối tượng và không chắc đối tượng
thực sự là thể hiện của lớp nào
- Ta cũng có thể gọi phương thức static của lớp type ,getType():
Type t = Type.GetType("System.Double");
Các thuộc tính của Type
1. 1 số thuộc tính lấy chuỗi chứa các tên khác nhau kết hợp với lớp :
Thuộc tính Trả về
Name tên của kiểu dữ liệu
FullName tên đầy đủ bao gồm cả namespace
Namespace tên namespace của kiểu dữ liệu.
2. Có thể lấy những tham chiếu đến kiểu những đối tượng mà trình bày các lớp có liên
quan :
Thuộc tính Kiểu tham chiếu trả về tương ứng với
BaseType kiểu cơ sở trực tiếp của kiểu này
UnderlyingSystemType kiểu mà kiểu này ánh xạ trong thời gian chạy .NET
3. 1 số thuộc tính luận lý chỉ định liệu có phải là kiểu nào đó hay không ví dụ là 1 lớp hay
1 kiểu liệt kê những thuộc tính này bao gồm : IsAbstract, IsArray,
IsClassembly,IsEnum, IsInterface, IsPointer, IsPrimitive ( 1 trong những kiểu dữ liệu
bẩm sinh được định nghĩa trước), IsPublic, IsSealed, and IsValueType.
Ví dụ :dùng kiểu dữ liệu bẩm sinh :
Type intType = typeof(int);Console.WriteLine(intType.IsAbstract); // writes false
Console.WriteLine(intType.IsClassembly); // writes false
Console.WriteLine(intType.IsEnum); // writes false
Console.WriteLine(intType.IsPrimitive); // writes true
Console.WriteLine(intType.IsValueType); // writes true
hoặc dùng lớp Vector :
Type intType = typeof(Vector);Console.WriteLine(intType.IsAbstract); //writes false
Console.WriteLine(intType.IsClassembly); // writes true
Console.WriteLine(intType.IsEnum); // writes false
Console.WriteLine(intType.IsPrimitive); // writes false
Console.WriteLine(intType.IsValueType); // writes false
Các phương thức :
Hầu hết các phương thức của System.Type được sử dụng để chứa chi tiết các thành viên
của kiểu dữ liệu tương ứng - hàm dựng ,thuộc tính,phương thức , biến cố có nhiều
phương thức nhưng tất cả chúng đều theo nền chung. ví dụ , có hai phương thức mà nhận
chi tiết phương thức của kiểu dữ li
ệu : getmethod() và getmethods(). getmethod() trả về 1
tham chiếu đến đối tượng System.Reflection.MethodInfo mà chứa chi tiết của 1 phương
thức .getmethods() trả vế 1 mảng tham chiếu. điểm khác là getmethods() trả về chi tiết
của tất cả phương thức , trong khi getmethod() trả về chi tiết của chỉ một phương thức
được chỉ định trong danh sách thông số.cả hai phương thức đều overloads mà lấy thêm 1
thông số , BindingFlags liệt kê giá trị chỉ định thành viên nào nên đượ
c trả về- ví dụ như
trả về 1 thành viên public, 1 thành viên instance, thành viên static
Ví dụ phương thức getmethods() không lấy thông số nào và trả về chi tiết của tất cả
phương thức thành viên của kiểu dữ liệu :
Type t = typeof(double);
MethodInfo [] methods = t.GetMethods();
foreach (MethodInfo nextMethod in methods)
{
// etc.
Kiểu đối tượng trả về Các phương thức (phương thức số nhiều ( có 's' ở cuối
tên ) trả về 1 mảng )
ConstructorInfo GetConstructor(), GetConstructors()
EventInfo GetEvent(), GetEvents()
Kiểu đối tượng trả về Các phương thức (phương thức số nhiều ( có 's' ở cuối
tên ) trả về 1 mảng )
FieldInfo GetField(), GetFields()
InterfaceInfo GetInterface(), GetInterfaces()
MemberInfo GetMember(), GetMembers()
MethodInfo GetMethod(), GetMethods()
PropertyInfo GetProperty(), GetProperties()
Phương thức getmember() và getmembers() trả về chi tiết của bất kì hay tất cả thành viên
của kiểu dữ liệu không cần biết đó là hàm dựng hay thuộc tính phương thức
Ví dụ TypeView
Qua ví dụ này ta có thể đưa ra một danh sách các thành viên của 1 kiểu dữ liệu. ta sẽ
minh họa dùng Typeview cho kiểu Double , và có thể thay thế bằng các kiểu khác .
typeview trình bày nhiều thông tin hơn trong console window :
Hộp thông điệp sẽ trình bày tên , tên đầu đủ namespace của kiểu dữ liệu sau đó lặp lại
xuyên suốt tất cả các thành viên thể hiện public của kiểu dữ liệu, trình bày mỗi thành viên
kiểu khai báo,kiểu thành viên ( phương thức, trường, ) và tên của thành viên.Declaring
type là tên c
ủa lớp mà thực sự khai báo kiểu thành viên.
Để bắt đầu ta thêm vài câu lệnh using:
using System;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
Ta cần System.text vì ta dùng Stringbuilder để xây dựng chuỗi trình bày trong message
box,và system.windoes.forms cho message box.toàn bộ mã nằm trong mainclassembly,
mà có 1 vài phương thức static và 1 trường static, 1 stringbuilder gọi outputText, mà
được dùng để tạo chuỗi trình bày trong message box, khai báo như sau :
class MainClass
{
static void Main()
{
// modify this line to retrieve details of any
// other data type
Type t = typeof(double);
AnalyzeType(t);
MessageBox.Show(OutputText.ToString(), "Analysis of type "
+ t.Name);
Console.ReadLine();
}
Đầu tiên ta khai báo đối tưọng Type để trình bày kiểu mà ta chọn.sau đó gọi phương thức
AnalyzeType(),mà lấy thông tin từ Type và dùng nó làm chuỗi xuất.cuối cùng trình bày
nó trong message box.
static void AnalyzeType(Type t)
{
AddToOutput("Type Name: " + t.Name);
AddToOutput("Full Name: " + t.FullName);
AddToOutput("Namespace: " + t.Namespace);
Type tBase = t.BaseType;
if (tBase != null)
AddToOutput("Base Type:" + tBase.Name);
Type tUnderlyingSystem = t.UnderlyingSystemType;
if (tUnderlyingSystem != null)
AddToOutput("UnderlyingSystem Type:" + tUnderlyingSystem.Name);
AddToOutput("\nPUBLIC MEMBERS:");
MemberInfo [] Members = t.GetMembers();
foreach (MemberInfo NextMember in Members)
{
AddToOutput(NextMember.DeclaringType + " " +
NextMember.MemberType + " " + NextMember.Name);
}
}
Phương thức này gọi những thuộc tính khác nhau của đối tượng Type để lấy thông tin ta
cần, liên quan đến tên , sau đó gọi Getmembers() để lấy 1 mảng đối tượng MemberInfo
mà ta có thể dùng để trình bày chi tiết của mỗi phưong thức. chú ý rằng ta dùng 1 phương
thức trợ giúp , AddTooutput(), để xây dựng chuỗi trình bày.
static void AddToOutput(string Text)
{
OutputText.Append("\n" + Text);
}
Lớp assembly
Lớp assembly được định nghĩa trong namespace System.Reflection , cho phép ta truy
xuất vào các metadata trong 1 assembly. Nó cũng chứa những phương thức cho phép ta
thực thi 1 assembly,. Như lớp Type, nó chứa 1 số lớn những phương thức và thuộc tính.ta
không thể xem xét hết.thay vào đó ta sẽ chỉ tìm hiểu một sồ phưong thức thuộc tính cần
thiết.
trước khi làm bất cứ điều gì với 1 thể hiện assembly ta cần tải nó vào tiến trình ch
ạy.ta
làm điều này bằng cách gọi phương thức static assembly.Load() và
assembly.LoadFrom(). điểm khác giữa 2 phương thức là load() lấy tên của assembly,mà
phải là assembly được tham chiếu từ assembly đang thực thi đương thời( nói cách khác
nó là assembly mà ta tham chiếu khi biên dịch dự án lần đầu.)trong khi loadfrom() lấy
đường dẫn của assembly,mà có thể là assembly bất kì được trình bày trong hệ thống :
Assembly assembly1 = Assembly.Load("SomeAssembly");
Assembly assembly2 = Assembly.LoadFrom
(@"C:\My Projects\GroovySoftware\SomeOtherAssembly");
Có 1 số cách overload khác của 2 phương thức này,mà thêm thông tin bảo mật.mỗi lần ta
nạp 1 assembly,ta có thể dùng những thuộc tính khác trên nó để tìm, ví dụ tên đầu đủ của
nó :
string name = assembly1.FullName;
Xem xét các kiểu được định nghĩa trong 1 assembly
1 khía cạnh hay của lớp assembly là nó cho phép ta lấy chi tiết tất cả các kiểu mà được
định nghĩa trong assembly tương ứng.ta đơn giản gọi assembly.getTypes() ,trả về 1 mảng
System.Type tham chiếu chứa tất cả các kiểu. ta có thể thao tác những tham chiếu kiểu
này như đối tượng Type dùng tác tử typeof hoặc Object.Gettype():
Type[] types = theassembly.GetTypes();
foreach(Type definedType in types)
DoSomethingWith(definedType);
Các thuộc tính tuỳ chọn.
Các phương thức ta dùng để tìm những thuộc tính tuỳ chọn được định nghĩa trên 1
assembly hoặc trên kiểu tuỳ thuộc vào kiểu của đối tượng đi kèm.nếu ta muốn tìm những
thuộc tính tuỳ chọn đi kèm với 1 assembly ,ta cần gọi phương thức static của lớp
attribute, GetCustomAttributes() truyền 1 tham chiếu đến assembly:
Attribute [] definedAttributes =
Attribute.GetCustomAttributes(assembly1);
// assembly1 là 1 đối tưọng assembly
GetCustomAttributes() đưọc dùng để lấy các thuộc tính của assembly, có vài overload :
nếu ta gọi không có đặc tả thông số thì nó trả về tất cả các thuộc tính tuỳ chọn được điịnh
nghĩa trong assembly đó.ta cũng có thể gọi nó bằng cách chỉ định thông số thứ hai, là 1
đối tưọng Type chỉ định lớp thuộc tính .trong trường hợp này GetCustomAttributes() trả
về 1 mảng bao gồm tất cả các thuộc tính trình bày của l
ớp đó.ta sẽ dùng overload này
trong ví dụ WhatsNewAttributes.để tìm các thuộc tính SupportsWhatsNew có được trình
bày trong assembly hay không.để làm điều này ta gọi GetCustomAttributes() truyền 1
tham chiếu đến assembly ,và kiểu thuộc tính SupportWhatsNewAttribute .nếu thuộc tính
này được trình bày , ta có 1 mảng chứa tất cả các thể hiện của nó.nếu không có trả về
null:
Attribute supportsAttribute =
Attribute.GetCustomAttributes(assembly1,
typeof(SupportsWhatsNewAttribute));
Lưu ý tất cả các attribute được lưu như các tham chiếu attribute.nếu ta muốn gọi bất kì
phương thức hay thuộc tính nào mà ta đã định nghĩa trong attribute,thì ta sẽ cần ép kiểu
những tham chiếu đó tường minh thành những lớp attribute tuỳ chọn có liên quan.ta có
thể lấy chi tiết của các attribute tuỳ chọn mà đi kèm với 1 kiểu dữ liệu đã cho bằng cách
gọi overload khác của assembly.GetCustomAttributes(), lần này truyền 1 tham chiếu
Type mà mô tả kiể
u mà ta muốn để lưu bất kì attribute đi kèm.mặt khác nếu ta muốn lấy
những attribute mà đi kèm với phương thức,hàm dựng,trường thì ta cần gọi
GetCustomAttributes() mà là thành viên của 1 trong những lớp
MethodInfo,ConstructorInfo, FieldInfo phần này nằm ngoài nội dung của chương.
Hoàn chỉnh ví dụ WhatsNewAttributes
Ta có thể hoàn chỉnh ví dụ này bằng cách viết thêm assembly cuối cùng trong ví dụ
assembly LookUpWhatsNew. phần này của ứng dụng là 1 ứng dụng console. tuy nhiên
,nó cần tham chiếu 2 assembly khác . mặc dù ta sắp viết 1 ưng dụng dòng lệnh, nhưng ta
sẽ làm theo ví dụ TypeView trước trình bày kết quả trong 1 message box.
tập tin được gọi LookUpWhatsNew.cs và lệnh để biên dịch nó là :
csc /reference:WhatsNewAttributes.dll /reference:VectorClassembly.dll
LookUpWhatsNew.cs
Trong phần mã của tập tin này ,đầu tiên ta chỉ định các namespace ta muốn dùng, trong
đó có system.Text vì ta cần sử dụng 1 đối t
ượng Stringbuilder lần nữa:
using System;
using System.Reflection;
using System.Windows.Forms;
using System.Text;
using Wrox.ProCSharp.VectorClassembly;
using Wrox.ProCSharp.WhatsNewAttributes;
namespace Wrox.ProCSharp.LookUpWhatsNew
{
lớp chính là WhatsNewChecker, mọi phương thức đều được định nghĩa trong lớp này,
gồm 2 trường static outputText chứa chuỗi xuất.backDateTo lưu ngày tháng mà ta chọn -
tất cả các cập nhật được tạo vào ngày này sẽ được trình bày thông thường ta sẽ cho
người dùng gõ ngày ,nhưng vì không muốn họ gõ sai nên ta sẽ lấy ngày 1 tháng 2 2002
làm ví dụ.
class WhatsNewChecker
{
static StringBuilder outputText = new StringBuilder(1000);
static DateTime backDateTo = new DateTime(2002, 2, 1);
static void Main()
{
Assembly theAssembly = Assembly.Load("VectorClass");
Attribute supportsAttribute =
Attribute.GetCustomAttribute(
theAssembly, typeof(SupportsWhatsNewAttribute));
string Name = theAssembly.FullName;
AddToMessage("Assembly: " + Name);
if (supportsAttribute == null)
{
AddToMessage(
"This assembly does not support WhatsNew attributes");
return;
}
else
AddToMessage("Defined Types:");
Type[] types = theAssembly.GetTypes();
foreach(Type definedType in types)
DisplayTypeInfo(theAssembly, definedType);
MessageBox.Show(outputText.ToString(),
"What\'s New since " + backDateTo.ToLongDateString());
Console.ReadLine();
}
Đầu tiên main() tải assembly VectorClassembly , xác minh xem nó có thực sự được đánh
dấu với attributeSupportsWhatsNew hay không.
Giả sử mọi thứ đều đúng ta dùng assembly.GetTypes() để lấy mảng tất cả các kiểu trong
assembly này, sau đó lặp qua chúng. trong mỗi lần lặp, ta gọi 1 phương thức mà ta viết
DisplayTypeInfo(), mà thêm 1 chuỗi có liên quan, bao gồm chi tiết các thể hiện của
LastModifiedAttribute, thành trường outputText. cuối cùng ta cho hiện message box
chuỗi này. phương thức DisplayTypeInfo() như sau:
static void DisplayTypeInfo(Assembly theAssembly, Type type)
{
// make sure we only pick out classes
if (!(type.IsClass))
return;
AddToMessage("\nclass " + type.Name);
Attribute [] attribs = Attribute.GetCustomAttributes(type);
if (attribs.Length == 0)
AddToMessage("No changes to this class\n");
else
foreach (Attribute attrib in attribs)
WriteAttributeInfo(attrib);
MethodInfo [] methods = type.GetMethods();
AddToMessage("CHANGES TO METHODS OF THIS CLASS:");
foreach (MethodInfo nextMethod in methods)
{
object [] attribs2 =
nextMethod.GetCustomAttributes(
typeof(LastModifiedAttribute), false);
if (attribs2 != null)
{
AddToMessage(
nextMethod.ReturnType + " " + nextMethod.Name + "()");
foreach (Attribute nextAttrib in attribs2)
WriteAttributeInfo(nextAttrib);
}
}
}
Lưu ý điều đầu tiên ta làm là kiểm tra tham chiếu Type được truyền vào có thực sự là 1
lớp. bởi vì ta đã chỉ định attributeLastModified chỉ có thể dùng cho lớp hoặc các phương
thức thành viên.
Kế tiếp ta dùng phương thức Attribute.GetCustomAttributes() để tìm xem nếu lớp có thể
hiện LastModifiedAttribute đi kèm với nó hay không.nếu có ta thêm vào chuỗi xuất.dùng
phương thức trợ giúp WriteAttributeInfo().
Cuối cùng ta dùng phương thức Type.GetMethods() để lặp tất cả các phương thứ
c thành
viên của kiểu dữ liệu này, sau đó làm giống như vậy với mỗi phương thức tiếp theo.
Phần mã tiếp theo thể hiện phương thức WriteAttributeInfo() tìm các chuỗi cần trình bày
trong thể hiện LastModifiedAttribute , vì phương thức này nhận 1 tham chiếu attribute
nên nó cần được ép kiểu thành tham chiếu LastModifiedAttribute.Sau khi xong dùng các
thuộc tính mà ta đã định nghĩa trong attribute này để lấy thông số của nó . nó kiểm tra
ngày của attribute trước khi thêm nó vào chuỗi được trình bày:
static void WriteAttributeInfo(Attribute attrib)
{
LastModifiedAttribute lastModifiedAttrib =
attrib as LastModifiedAttribute;
if (lastModifiedAttrib == null)
return;
// Kiểm tra ngày tháng hợp lệ
DateTime modifiedDate = lastModifiedAttrib.DateModified;
if (modifiedDate < backDateTo)
return;
AddToMessage(" MODIFIED: " +
modifiedDate.ToLongDateString() + ":");
AddToMessage(" " + lastModifiedAttrib.Changes);
if (lastModifiedAttrib.Issues != null)
AddToMessage(" Outstanding issues:" +
lastModifiedAttrib.Issues);
AddToMessage("");
}
Kết quả sau khi chạy :
Lưu ý rằng khi ta liệt kê các kiểu được định nghĩa trong assembly VectorClassembly ta
thực sự lấy hai lớp : Vector và lớp VectorEnumerator kèm theo mà ta đã xây dựng trong
phần trước.
Code for Download:
TypeView
WhatsNewAttributes
. type.type có 1 lớp
dẫn xuất đáp ứng mỗi kiểu dữ liệu. có 3 cách lấy 1 tham chiếu Type mà chuyển cho kiểu
dữ liệu bất kì:
- Dùng tác tử typeof. tác tử này. tính Trả về
Name tên của kiểu dữ liệu
FullName tên đầy đủ bao gồm cả namespace
Namespace tên namespace của kiểu dữ liệu.
2. Có thể lấy những tham