Mastering Microsoft Visual Basic 2010 phần 5 docx

105 349 0
Mastering Microsoft Visual Basic 2010 phần 5 docx

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

388 CHAPTER 10 APPLIED OBJECT-ORIENTED PROGRAMMING type, there’s a set of local variables, as they’re declared in the class code. The various proce- dures of the class are invoked as needed by the Common Language Runtime (CLR) and they act on the set of local variables that correspond to the current instance of the class. Some of the local variables may be common among all instances of a class: These are the variables that correspond to shared properties (properties that are being shared by all instances of a class). When you create a new variable of the Customer type, the New() procedure of the Cus- tomer class is invoked. The New() procedure is known as the class constructor. Each class has a default constructor that accepts no arguments, even if the class doesn’t contain a New() sub- routine. This default constructor is invoked every time a statement similar to the following is executed: Dim cust As New Customer You can overload the New() procedure by specifying arguments, and you should try to pro- vide one or more parameterized constructors. Parameterized constructors allow you (or any developer using your class) to create meaningful instances of the class. Sure, you can create a new Customer object with no data in it, but a Customer object with a name and company makes more sense. The parameterized constructor initializes some of the most characteristic properties of the object. Objects versus Object Variables All variables that refer to objects are called object variables. (The other type of variables are value variables, which store base data types, such as characters, integers, strings, and dates.) In declaring object variables, we usually use the New keyword, which is the only way to cre- ate a new object. If you omit this keyword from a declaration, only a variable of the Customer type will be created, but no instance of the Customer class will be created in memory, and the variable won’t point to an actual object. The following statement declares a variable of the Cus- tomer type, but doesn’t create an object: Dim Cust As Customer If you attempt to access a member of the Customer class through the Cust variable, the infa- mous NullReferenceException will be thrown. The description of this exception is Object reference not set to an instance of an object, which means that the Cust variable doesn’t point to an instance of the Customer class. Actually, the editor will catch this error and will underline the name of the variable. If you hover over the name of the variable in question, the follow- ing explanation will appear on a ToolTip box: Variable Cust is used before it has been assigned a value. A Null Reference exception could result at runtime. Why bother declaring variables that don’t point to specific objects? The Cust variable can be set later in the code to reference an existing instance of the class: Dim Cust As Customer Dim Cust2 As New Customer Cust = Cust2 After the execution of the preceding statements, both variables point to the same object in memory, and you can set the properties of this object through either variable. You have two object variables but only one object in memory because only one of them was declared with the ISSUES IN OBJECT-ORIENTED PROGRAMMING 389 New keyword. To set the Company property, you can use either one of the following statements because they both point to the same object in memory: Cust.CompanyName = "New Company Name" or Cust2.CompanyName = "New Company Name" The Cust variable is similar to a shortcut. When you create a shortcut to a specific file on your desktop, you’re creating a reference to the original file. You do not create a new file or a copy of the original file. You can use the shortcut to access the original file, just as you can use the Cust variable to manipulate the properties of the Cust2 object in the preceding code sample. It’s also common to declare object variables without the New keyword when you know you’re going to use them later in your code, as shown in the following loop, which creates 20 items and adds them to a ListView control: Dim LI As ListViewItem For row = 0 To 20 LI = New ListViewItem LI.Text = " " ‘ more statements to set up the LI variable ListView1.Items.Add(LI) Next The LI variable is declared once, and the code initializes it many times in the following loop. The first statement in the loop creates a new ListViewItem object, and the last statement adds it to the ListView control. Another common scenario is to declare an object variable without initializing it at the form’s level and initialize it in a procedure while using its value in several procedures. When to Use the New Keyword Many programmers are confused by the fact that most object variables must be declared with the New keyword, whereas some types don’t support the New keyword. If you want to create a new object in memory (which is an instance of a class), you must use the New keyword. When you declare a variable without the New keyword, you’re creating a reference to an object, but not a new object. Only shared classes must be declared without the New keyword. If in doubt, use the New keyword anyway, and the compiler will let you know immediately whether the class you’re instantiating has a constructor. If the New keyword is underlined in error, you know that you must delete it from the declaration. The Math class, for example, is shared and you cannot create a new instance of it; instead, you can call its members as Math.Log, Math.Exp,andsoon. 390 CHAPTER 10 APPLIED OBJECT-ORIENTED PROGRAMMING Uninitialized and Nullable Variables As you already know, an object variable may exist but not be initialized. The following state- ment creates a new variable for storing a Brush object (one of the drawing objects discussed in the tutorial on graphics that accompanies this book): Dim B As SolidBrush The B variable’s value is Nothing because it hasn’t been initialized yet. After execut- ing the following statement, the B variable will have a value and can be used to draw something: B = New SolidBrush(Color.Blue) To find out whether a variable has been initialized or not, we use the Is operator to com- pare the variable to Nothing: If B Is Nothing Then MsgBox("Uninitialized Brush variable") End If Alternatively, you can use the IsNot operator before attempting to use the B variable: If B IsNot Nothing Then ‘ draw something with the brush End If When a variable is Nothing, we know that it has not been initialized yet — the variable has no value. In my view, this is a state of a variable: a variable may have a value (any value) or not have a value. Let’s consider an Integer and a String variable declared as follows: Dim Age As Integer Dim Name As String The Age and Name variables have not been initialized explicitly, but they do have a value. Integers are initialized to zero and strings are initialized to empty strings. But is this what we really need? In many cases we want to know whether a variable has been initialized or not, and a default value just doesn’t cut it. A variable that has no value is not necessarily a numeric zero or an empty string. To differentiate between the default values and the lack of value, the Framework supports the Nullable type, which indicates a variable of any of the basic types that will not be initialized implicitly. The Nullable keyword is followed by a pair of parentheses and the Of keyword, followed by the actual type. The following statement declares an Integer variable that is not initialized: Dim Age As Nullable(Of Integer) Unfortunately, strings are not nullable. The advantage of using Nullable types in your code is that this type exposes the HasValue property, which returns True if the variable has been ISSUES IN OBJECT-ORIENTED PROGRAMMING 391 initialized, and the Value property that returns the actual variable type. This is how you would process the Age variable in your code: Dim Age As Nullable(Of Integer) ’ other statements Dim Qualifies As Boolean If Age.HasValue Then If Age.Value < 16 Then Qualifies = False Else Qualifies = True End If There’s also a shorthand notation for declaring Nullable types; just append a question mark to the variable’s name as in the following statement: Dim Age? As Integer Missing Information As for a practical example of variables that might not have values, there are plenty. Databases use a special value to indicate that a field has no specific value, the Null value. If a product’s price is not known yet, or if a book’s page count is unknown, this d oesn’t mean the product will be sold at no cost or that we’ll display that the book has zero pages when customers look it up. These fields have a Null value and should be handled differently, as you will see in Chapter 15, ‘‘Programming with ADO.NET.’’ The same is true for data we read from external sources. An XML file with customer information may contain one or more entries without avaluefortheEMail field. This customer’s email address is missing (Null or Nothing) and certainly not an empty string. When you set up variables to receive data from a database, you will find the Nullable type very accommodating because it allows you to match not only the usual values, but also the lack of a specific value. Exploring Value Types Okay, if the variables that represent objects are called object variables and the types they rep- resent are called reference types, what other variables are there? There are the regular variables that store the basic data types, and they’re called value variables because they store values. An integer, or a string, is not stored as an object for efficiency. An Integer variable contains an actual value, not a pointer to the value. Imagine if you had to instantiate the Integer class every time you needed to use an integer value in your code. Not that it’s a bad idea, but it would scare away most VB developers. Value variables are so common in programming and they’re not implemented as classes for efficiency. Whereas objects require complicated structures in memory, the basic data types are stored in a few bytes and are manipulated much faster than objects. 392 CHAPTER 10 APPLIED OBJECT-ORIENTED PROGRAMMING Consider the following statements: Dim age1, age2 As Integer age2 = 29 age1 = age2 age2 = 40 When you assign a value variable to another, the actual value stored in the variable overwrites the current value of the other variable. The two variables have the same value after the statement that assigns the value of age2 to the variable age1, but they’re independent of one another. After the execution of the last statement, the values of age1 and age2 are different again. If they were object variables, they would point to the same object after the assignment operation, and you wouldn’t be able to set their values separately. You’d be setting the properties of the same object. Value types are converted to objects as soon as you treat them as objects. As soon as you enter a statement like the following, the age1 variable is converted to an object: age1.MinValue You’ll rarely use the methods of the base types, except for the ToString method of course, but you can turn value variables into object variables at any time. This process is known as boxing (the conversion of a value type to a reference type). Exploring Reference Types To better understand how reference types work, consider the following statements that append a new row with two subitems to a ListView control (the control’s item is an object of the ListViewItem type): ListView1.Items.Clear Dim LI As New ListViewItem LI.Text = "Item 1" LI.SubItems.Add("Item 1 SubItem 1.a") LI.SubItems.Add("Item 1 SubItem 1.b") ListView1.Items.Add(LI) After the execution of the preceding statements, the ListView control contains a single row. This row is an object of the ListViewItem type and exists in memory on its own. Only after the execution of the last statement is the ListViewItem object referenced by the LI variable associ- ated with the ListView1 control. To change the text of the first item, or its appearance, you can manipulate the control’s Items collection directly or change the LI variable’s properties. The following pairs of statements are equivalent: ListView1.Items(0).Text = "Revised Item 1" ListView1.Items(0).BackColor = Color.Cyan LI.Text = "Revised Item 1" LI.BackColor = Color.Cyan ISSUES IN OBJECT-ORIENTED PROGRAMMING 393 There’s yet another method to access the ListView control’s items. Create an object variable that references a specific item and set the item’s properties through this variable: Dim selItem As ListViewItem selItem = ListView1.Items(0) selItem.Text = "new caption" selItem.BackColor = Color.Silver (If you need more information on using the ListView and TreeView controls, please refer to the tutorial ‘‘The ListView and TreeView Controls,’’ which is available for download from www.sybex.com/go/masteringvb2010. A final question for testing your OOP skills: What do you think will happen if you set the LI variable to Nothing? Should the control’s row disappear? The answer is no. If you thought otherwise, take a moment now to think about why deleting a variable doesn’t remove the object from memory. The LI variable points to an object in memory; it’s not the object. The New key- word created a new ListViewItem object in memory and assigned its address to the variable LI. The statement that added the LI variable to the control’s Items collection associated the object in memory with the control. By setting the LI variable to Nothing, we simply removed the pointer to the ListViewItem object in memory, not the object itself. To actually remove the control’s first item, you must call the Remove method of the LI variable: LI.Remove This statement will remove the ListViewItem object from the control’s Items collection, but the actual object still lives in the memory. If you execute the following statement, the item will be added again to the control: ListView1.Items.Add(LI) So to sum up, the ListViewItem object exists in memory and is referenced by the LI variable as well as by the ListView control. The Remove method removes the item from the control; it doesn’t delete it from the memory. If you remove the item from the control and then set the LI variable to Nothing, the object will also be removed from memory. Another way to look at the LI variable is as an intermediate variable. You could add a new row to the ListView control in a single statement without the intermediate variable: ListView1.Items.Add(New ListViewItem("item header")) By the way, the ListViewItem object won’t be deleted instantly. The CLR uses a special mechanism to remove objects from memory, the Garbage Collector (GC). The GC runs every so often and removes from memory all objects that are not referenced by any variable. These objects eventually will be removed from memory, but we can’t be sure when. (There’s no way to force the GC to run on demand.) The CLR will start the GC based on various criteria (the current CPU load, the amount of available memory, and so on). Because objects are removed automatically by the CLR, we say that the lifetime of an object is nondeterministic .Weknow when the object is created, but there’s no way to know, or specify, when it’s deleted. However, you can rest assured that the object will eventually be removed from memory. After you set the LI variable to Nothing and remove the corresponding item from the ListView control, you’re left with a ListViewItem object in memory that’s not referenced by any other entity. 394 CHAPTER 10 APPLIED OBJECT-ORIENTED PROGRAMMING This object will live a little longer in the memory, until the GC gets a chance to remove it and reclaim the resources allocated to it. Moreover, once you have removed the references to the object, there’s no way to access the object any more, even though it will exist for a while in memory before the GC gets a chance to destroy it. Listing 10.1 shows the statements I’ve used for this experiment. Listing 10.1: Creating and removing objects ’ Create a new ListViewItem object Dim LI As New ListViewItem LI.Text = "Item 1" LI.SubItems.Add("Item 1 SubItem 1.a") LI.SubItems.Add("Item 1 SubItem 1.b") ’ add it to the ListView control ListView1.Items.Add(LI) MsgBox("Item added to the list." & vbCrLf & "Click OK to modify the appearance " & "of the top item through the LI variable.") ’ Edit the object’s properties ’ The new settings will affect the appearance of the ’ item on the control immediately LI.Text = "ITEM 1" LI.Font = New Font("Verdana", 10, FontStyle.Regular) LI.BackColor = Color.Azure MsgBox("Item’s text and appearance modified. " & vbCrLf & "Click OK to modify the " & "appearance of the top item through " & "the ListView1.Items collection.") ’ Change the first item on the control directly ’ Changes also affect the object in memory ListView1.Items(0).BackColor = Color.LightCyan LI.SubItems(2).Text = "Revised Subitem" ’ Remove the top item from the control MsgBox("Will remove the top item from the control.") LI.Remove() MsgBox("Will restore the deleted item") ’ The item was removed from list, but not deleted ’ We can add it to the control’s Items collection ListView1.Items.Add(LI) MsgBox("Will remove object from memory") ’ Remove it again from the control LI.Remove() ’ and set it to Nothing LI = Nothing ’ We can no longer access the LI object. MsgBox("Can I access it again?" & vbCrLf & "NO, YOU’LL GET AN EXCEPTION WHEN THE " & "FOLLOWING STATEMENT IS EXECUTED!") ListView1.Items.Add(LI) ISSUES IN OBJECT-ORIENTED PROGRAMMING 395 Properties versus Fields When you set or read a property’s value, the corresponding Get or Set segment of the Property procedure is executed. The following statement invokes the Property Set segment of the EMail public property of the class: cust.EMail = "Evangelos.P@Sybex.com" As a reminder, even if the EMail property is an auto-implemented property, a Property pro- cedure is invoked behind the scenes and sets the value of a local variable (the _EMail variable). Obviously, every time you call one of the class properties, the corresponding public procedure in the class is invoked. The following statement invokes both the Set and Get Property proce- dures of the Customer class Balance property: cust.Balance = cust.Balance + 429.25 Trivial properties can also be implemented as public variables. These variables, which are called fields, behave like properties, but no code is executed when the application sets or reads their value. We often implement properties of the enumeration type as fields because they can be set only to valid values and there’s no need for validation code. If the Set method of a prop- erty doesn’t contain any validation code and simply assigns a new value to the local variable that represents the specific property, there’s no difference between the property and a field. If you don’t plan to validate the values of certain properties, use auto-implemented properties, which are as simple as fields. Shared versus Instance Members To really understand classes and appreciate them, you must visualize the way classes com- bine code and data. Properties contain the data that live along with the code, which determines the object’s behavior — its functionality. The functionality of the object is implemented as a number of methods and events. The properties, methods, and events constitute the class’s inter- face. Each instance of the class acts on its own data, and there’s no interference between two objects of the same type unless they contain shared properties. A shared property is common to all instances of the class. In other words, there’s no local variable for this property, and all instances of the class access the same variable. Shared properties are not common — after all, if many of the properties are common to all instances of the class, why create many objects? Shared methods, on the other hand, are quite common. The Math class is a typical example. To calculate the logarithm of a number, you call the Log method of the Math class: Math.Log(123) You need not create an instance of the Math class before calling any of its methods (which are the common math functions). Actually, you can’t create a new instance of the Math class because the entire class is marked as shared. Let’s say you’re building a class to represent customers, the Customer class. This class should expose properties that correspond to the columns of the Customers table in a database. Each instance of the Customer class stores information about a specific customer. In addition to the properties, the Customer class should expose a few methods to get data from the database and commit changes or new customers to the database. The GetCustomerByID method, for example, should accept the ID of a customer as an argument, retrieve the corresponding 396 CHAPTER 10 APPLIED OBJECT-ORIENTED PROGRAMMING customer’s data from the database, and use them to populate the current instance’s properties. Here’s how you use this class in your code: Dim cust As New Customer cust.GetCustomerByID("ALFKI") Debug.WriteLine cust.CompanyName Debug.WriteLine cust.ContactName & " " & cust.ContactTitle The GetCustomerByID method can retrieve the customer data from a local database, a remote web service, or even an XML file. The idea is that a single method call gets the data and uses it to populate the properties of the current instance of the class. This method is an instance method because it requires an instance of the class. It populates the properties of this instance, or object. You could have implemented the GetCustomerByID method as a shared method, but then the method should return an object of the Customer type. The shared method can’t populate any object’s properties because it can’t be applied to an instance of the class. Here’s how you’d use the Customer class if the GetCustomerByID method were shared: Dim cust As New Customer cust = Customer.GetCustomerByID("ALFKI") Debug.WriteLine cust.CompanyName Debug.WriteLine cust.ContactName & " " & cust.ContactTitle As you can see, you call the method of the Customer class, not the method of an object. You could also call the method with the following statement, but the code becomes obscure (at the very least, it’s not elegant): cust = cust.GetCustomerByID("ALFKI") The background compiler will detect that you’re attempting to access a shared method through an instance of the class and will generate the following warning (the expression will be evaluated at runtime, in spite of the warning): Access of shared member, constant member, enum member or nested type through an instance; qualifying expression will not be evaluated. Because the class needs to know the database in which the data is stored, you can provide a Connection property that’s shared. Shared properties are usually set when the class is initial- ized or from within a method that’s called before we attempt to access any other methods or any of the class’s properties. All the methods in the class use the Connection property to con- nect to the database. There’s no reason to change the setting of this property in the course of an application, but if you change it, all subsequent operations will switch to the new database. In summary, a class may expose a few shared properties if all instances of the class should access the same property value. It may also expose a few shared methods, which can be called through the class name if there’s no need to create an instance of the class in order to call a method. In extreme situations, you can create a shared class: All properties and methods of this class are shared by default. To make the most of objects, however, you should create classes that are instantiated with the New keyword and methods that manipulate the current instance of the class. ISSUES IN OBJECT-ORIENTED PROGRAMMING 397 Type Casting The data type used most in earlier versions of the language up to VB 6 was the Variant (which was replaced in subsequent versions by the Object type). A variable declared as Object can store anything, and any variable that hasn’t been declared explicitly is an Object variable. Even if you turn on the Strict option, which forces you to declare the type of each variable (and you should always have this option on), you will eventually run into Object variables. When you retrieve an item from a ListBox control, for example, you get back an object, not a specific data type. In the previous chapter, we used the ListBox control to store Contact objects. Every time we retrieved a contact from the control’s Items collection, however, we got back an Object vari- able. To use this object in our code, we had to convert it to a more specific type, the Contact type, with the CType() or DirectCast function. The same is true for an ArrayList, which stores objects, and we usually cast its members to specific types. Variables declared without a specific type are called untyped variables. Untyped variables should be avoided — and here’s why. The following expression represents a ListBox item, which is an object: ListBox1.Items(3) Even if you add a Customer or a Product object to the list, when you retrieve the same item, it’s returned as a generic Object variable. If you type the preceding expression followed by a period, you will see in the IntelliSense drop-down list the members of the generic Object vari- able, which you hardly ever need. If you cast this item to a specific type, the IntelliSense box will show the members of the appropriate type. The action of changing a variable’s type is known as casting, and there are two methods for casting variable types — the old VB 6 CType() function and the new DirectCast() function: Dim currentCustomer As Customer currentCustomer = DirectCast(ListBox1.Items(3), Customer) From now on, you can access the members of the currentCustomer variable as usual. The TryCast() Function If the specified type conversion can’t be carried out, the CType() function will throw an InvalidCastException exception. As a reminder, a variation of the CType() and DirectCast() functionsistheTryCast() function, which attempts to convert a variable into another type. If the conversion is not possible, the TryCast() function doesn’t throw an exception but returns the Nothing value. Here’s how the TryCast() function is used: Dim o As Object o = New Customer("Evangelos Petroutsos", "SYBEX") c = TryCast(o, Contact) If c Is Nothing Then MsgBox("Can’t convert " & o.GetType.Name & " to Contact") Exit Sub End If ’ statements to process the c object variable [...]... than 50 : integers.Where(Function(k) k < 50 ) 409 410 CHAPTER 10 APPLIED OBJECT-ORIENTED PROGRAMMING The expression k < 50 is evaluated for each element of the array and, if smaller, the value is selected Otherwise, it’s ignored Having selected the ‘‘small’’ values in the array, we can apply the Sum method to calculate the sum of the selected values: Dim smallSum = integers.Where(Function(k) k < 50 ).Sum... Public Overrides Function Perimeter() As Double Return (4 * _Side) End Function End Class 4 15 416 CHAPTER 10 APPLIED OBJECT-ORIENTED PROGRAMMING The Shapes.vb file, available for download from www.sybex.com/go/masteringvb2010, contains three classes: the Square, Triangle, and Circle classes All three expose their basic geometric characteristics as properties The Triangle class, for example, exposes the... entities won’t change Let’s look at a custom class for storing products, which is part of the Products sample project, available for download from www.sybex.com/go/masteringvb2010 The application’s main form is shown in Figure 10.1 The most basic class stores the information you’ll need in ordering and invoicing applications: the product’s ID, its name, and its price Here’s the implementation of a simple... to create an expression that sums the squares of selected elements in the array To request the sum of the squares of all values that are numerically less than 50 , use the following expression: Dim smallSqSum = integers.Where(Function(k) k < 50 ).Sum(Function(v) v ˆ 2) The Where extension method selects the desired values and the Sum extension method acts on them The Where method returns an IEnumerable... Perimeter method, and they must provide the implementation of these methods The code shown in Listing 10 .5 comes from the Shapes sample project The application’s main form, which exercises the Shape class and its derived classes, is shown in Figure 10.3 Figure 10.3 The main form of the Shapes project Listing 10 .5: Shape class Class Shape Overridable Function Area() As Double End Function Overridable Function... average of the elements in the array (provided that the array is of a numeric type) The following statement sets up a small array of integers: Dim integers() = {1, 84, 12, 27, 3, 19, 73, 9, 16, 41, 53 , 57 , 13} To calculate the sum of its elements you can write a For Each loop to iterate through all the elements of the array, as usual, or call the Sum method: Dim sumOfIntegers = integers.Sum Likewise,... Function Public MustOverride Function Method4() As String ’ No code in a member that must be overridden ! ’ Notice the lack of the matching End Function here Public Function Method5() As String Return ("I’m the original Method5") End Function Private prop1, prop2 As String Property Property1() As String Get Property1 = "Original Property1" End Get Set prop1 = Value End Set End Property Property Property2()... The Array class, for example, can’t be inherited and neither can the String class If you need to add a few methods to a class that are specific to an application, you can use extension methods With VB 2010 you can add a method to any class without even inheriting it You don’t have to create a new class, just a module that contains one or more procedures that accept the type of the class you want to... write applications to maintain all this information To sell the same application to an electronics store, you must write another module for maintaining a different type of product, but the table with the basic data remains the same Clearly, you can’t design a program for handling all types of products, nor can you edit the same application to fit different products You just have to write different applications... TextBox control to address specific application requirements Many developers add a few statements in the control Enter and Leave events to change the color of the TextBox control that has the focus With VB 2010, it’s possible to write just two event handlers that react to these two events and control the background color of the TextBox with the focus These two handlers handle the corresponding events of . sample project, available for download from www.sybex.com/go/masteringvb2010. The application’s main form is shown in Figure 10.1. The most basic class stores the information you’ll need in ordering. ‘‘The ListView and TreeView Controls,’’ which is available for download from www.sybex.com/go/masteringvb2010. A final question for testing your OOP skills: What do you think will happen if you set. reference types, what other variables are there? There are the regular variables that store the basic data types, and they’re called value variables because they store values. An integer, or a

Ngày đăng: 12/08/2014, 21:20

Từ khóa liên quan

Mục lục

  • Mastering Microsoft Visual Basic 2010

    • Part 3: Working with Custom Classes and Controls

      • Chapter 10: Applied Object-Oriented Programming

        • Inheritance

        • Extension Methods

        • Polymorphism

        • Who Can Inherit What?

        • The Bottom Line

        • Part 4: Working with the .NET Framework

          • Chapter 11: The Framework at Large

            • What Is the Framework?

            • Using Snippets

            • Using the My Component

            • The IO Namespace

            • Drawing and Painting

            • The Image Class

            • Printing

            • Handling Strings and Characters

            • Handling Dates and Time

            • The Bottom Line

Tài liệu cùng người dùng

Tài liệu liên quan