Home Random Page


CATEGORIES:

BiologyChemistryConstructionCultureEcologyEconomyElectronicsFinanceGeographyHistoryInformaticsLawMathematicsMechanicsMedicineOtherPedagogyPhilosophyPhysicsPolicyPsychologySociologySportTourism






Nbsp;   Parameterless Properties

Many types define state information that can be retrieved or altered. Frequently, this state informa- tion is implemented as field members of the type. For example, here’s a type definition that contains two fields.

 

public sealed class Employee {

public String Name; // The employee's name public Int32 Age; // The employee's age

}

 

If you were to create an instance of this type, you could easily get or set any of this state informa- tion with code similar to the following.

 

Employee e = new Employee();

e.Name = "Jeffrey Richter"; // Set the employee's Name. e.Age = 48; // Set the employee's Age.

 

Console.WriteLine(e.Name); // Displays "Jeffrey Richter"


Querying and setting an object’s state information in the way I just demonstrated is very common.

However, I would argue that the preceding code should never be implemented as shown. One of the hallmarks of object-oriented design and programming is data encapsulation. Data encapsulation means that your type’s fields should never be publicly exposed because it’s too easy to write code that improperly uses the fields, corrupting the object’s state. For example, a developer could easily corrupt an Employee object with code like the following.

 

e.Age = ­5; // How could someone be –5 years old?

 

There are additional reasons for encapsulating access to a type’s data field. For example, you might want access to a field to execute some side effect, cache some value, or lazily create some internal object. You might also want access to the field to be thread-safe. Or perhaps the field is a logical field whose value isn’t represented by bytes in memory but whose value is instead calculated using some algorithm.

For any of these reasons, when designing a type, I strongly suggest that all of your fields be private. Then, to allow a user of your type to get or set state information, you expose methods for that specific purpose. Methods that wrap access to a field are typically called accessor methods.

These accessor methods can optionally perform sanity checking and ensure that the object’s state is never corrupted. For example, I’d rewrite the previous class as follows.

 

public sealed class Employee {

private String m_Name; // Field is now private private Int32 m_Age; // Field is now private

 

public String GetName() { return(m_Name);

}

 

public void SetName(String value) { m_Name = value;

}

 

public Int32 GetAge() { return(m_Age);

}

 

public void SetAge(Int32 value) { if (value < 0)

throw new ArgumentOutOfRangeException("value", value.ToString(), "The value must be greater than or equal to 0");

m_Age = value;

}

}

 

Although this is a simple example, you should still be able to see the enormous benefit you get from encapsulating the data fields. You should also be able to see how easy it is to make read-only or write-only properties: just don’t implement one of the accessor methods. Alternatively, you could allow only derived types to modify the value by marking the SetXxx method as protected.




Encapsulating the data as shown earlier has two disadvantages. First, you have to write more code because you now have to implement additional methods. Second, users of the type must now call methods rather than simply refer to a single field name.

 

e.SetName("Jeffrey Richter"); // Updates the employee's name String EmployeeName = e.GetName(); // Retrieves the employee's name e.SetAge(48); // Updates the employee's age

e. SetAge(­5); // Throws ArgumentOutOfRangeException Int32 EmployeeAge = e.GetAge(); // Retrieves the employee's age

 

Personally, I think these disadvantages are quite minor. Nevertheless, programming languages and the CLR offer a mechanism called properties that alleviates the first disadvantage a little and removes the second disadvantage entirely.

The class shown here uses properties and is functionally identical to the class shown earlier.

 

public sealed class Employee { private String m_Name; private Int32 m_Age;

 

public String Name {

get { return(m_Name); }

set { m_Name = value; } // The 'value' keyword always identifies the new value.

}

 

public Int32 Age {

get { return(m_Age); } set {

if (value < 0) // The 'value' keyword always identifies the new value. throw new ArgumentOutOfRangeException("value", value.ToString(),

"The value must be greater than or equal to 0"); m_Age = value;

}

}

}

 

As you can see, properties complicate the definition of the type slightly, but the fact that they al- low you to write your code as follows more than compensates for the extra work.

 

e.Name = "Jeffrey Richter"; // "Sets" the employee name String EmployeeName = e.Name; // "Gets" the employee's name e.Age = 48; // "Sets" the employee's age

e.Age = ­5; // Throws ArgumentOutOfRangeException Int32 EmployeeAge = e.Age; // "Gets" the employee's age

 

You can think of properties as smart fields: fields with additional logic behind them. The CLR supports static, instance, abstract, and virtual properties. In addition, properties can be marked with any accessibility modifier (discussed in Chapter 6, “Type and Member Basics”) and defined within an interface (discussed in Chapter 13, “Interfaces”).


Each property has a name and a type (which can’t be void). It isn’t possible to overload properties (that is, have two properties with the same name if their types are different). When you define a prop- erty, you typically specify both a get and a set method. However, you can leave out the set method to define a read-only property or leave out the get method to define a write-only property.

It’s also quite common for the property’s get/set methods to manipulate a private field defined within the type. This field is commonly referred to as the backing field. The get and set methods don’t have to access a backing field, however. For example, the System.Threading.Thread type offers a Priority property that communicates directly with the operating system; the Thread object doesn’t maintain a field for a thread’s priority. Another example of properties without backing fields are those read-only properties calculated at run time—for example, the length of a zero-terminated array or the area of a rectangle when you have its height and width.

When you define a property, depending on its definition, the compiler will emit either two or three

of the following items into the resulting managed assembly:

 

■ A method representing the property’s get accessor method. This is emitted only if you define

a get accessor method for the property.

 

■ A method representing the property’s set accessor method. This is emitted only if you define

a set accessor method for the property.

 

■ A property definition in the managed assembly’s metadata. This is always emitted.

 

Refer back to the Employee type shown earlier. As the compiler compiles this type, it comes across the Name and Age properties. Because both properties have get and set accessor methods, the com- piler emits four method definitions into the Employee type. It’s as though the original source were written as follows.

 

public sealed class Employee { private String m_Name; private Int32 m_Age;

 

public String get_Name(){ return m_Name;

}

public void set_Name(String value) {

m_Name = value; // The argument 'value' always identifies the new value.

}

 

public Int32 get_Age() { return m_Age;

}

 

public void set_Age(Int32 value) {

if (value < 0) // The 'value' always identifies the new value. throw new ArgumentOutOfRangeException("value", value.ToString(),

"The value must be greater than or equal to 0"); m_Age = value;

}

}


The compiler automatically generates names for these methods by prepending get_ or set_ to

the property name specified by the developer.

 

C# has built-in support for properties. When the C# compiler sees code that’s trying to get or set a property, the compiler actually emits a call to one of these methods. If you’re using a programming

language that doesn’t directly support properties, you can still access properties by calling the desired accessor method. The effect is exactly the same; it’s just that the source code doesn’t look as pretty.

In addition to emitting the accessor methods, compilers also emit a property definition entry into the managed assembly’s metadata for each property defined in the source code. This entry contains some flags and the type of the property, and it refers to the get and set accessor methods. This information exists simply to draw an association between the abstract concept of a “property” and its accessor methods. Compilers and other tools can use this metadata, which can be obtained by using the System.Reflection.PropertyInfo class. The CLR doesn’t use this metadata information and requires only the accessor methods at run time.

 

Automatically Implemented Properties

If you are creating a property to simply encapsulate a backing field, then C# offers a simplified syntax

known as automatically implemented properties (AIPs), as shown here for the Name property.

 

public sealed class Employee {

// This property is an automatically implemented property public String Name { get; set; }

 

private Int32 m_Age;

 

public Int32 Age {

get { return(m_Age); } set {

if (value < 0) // The 'value' keyword always identifies the new value. throw new ArgumentOutOfRangeException("value", value.ToString(),

"The value must be greater than or equal to 0"); m_Age = value;

}

}

}

 

When you declare a property and do not provide an implementation for the get/set methods, then the C# compiler will automatically declare for you a private field. In this example, the field will be of type String, the type of the property. And, the compiler will automatically implement the get_Name and set_Name methods for you to return the value in the field and to set the field’s value, respectively.

You might wonder what the value of doing this is, as opposed to just declaring a public String field called Name. Well, there is a big difference. Using the AIP syntax means that you have created a property. Any code that accesses this property is actually calling get and set methods. If you decide later to implement the get and/or set method yourself instead of accepting the compiler’s default


implementation, then any code that accesses the property will not have to be recompiled. However, if you declared Name as a field and then you later change it to a property, then all code that accessed the field will have to be recompiled so that it now accesses the property methods.

■ Personally, I do not like the compiler’s AIP feature, so I usually avoid it for the following reason: The syntax for a field declaration can include initialization so that you are declaring and initial- izing the field in one line of code. However, there is no convenient syntax to set an AIP to an initial value. Therefore, you must explicitly initialize each AIP in each constructor method.

■ The runtime serialization engines persist the name of the field in a serialized stream. The name of the backing field for an AIP is determined by the compiler, and it could actually change the name of this backing field every time you recompile your code, negating the ability to deseri- alize instances of any types that contain an AIP. Do not use the AIP feature with any type you intend to serialize or deserialize.

■ When debugging, you cannot put a breakpoint on an AIP get or set method, so you cannot easily detect when an application is getting or setting this property. You can set breakpoints on manually implemented properties, which can be quite handy when tracking down bugs.

You should also know that when you use AIPs, the property must be readable and writable; that is, the compiler must produce both get and set methods. This makes sense because a write-only field is not useful without the ability to read its value; likewise, a read-only field would always have its default value. In addition, because you do not know the name of the compiler-generated backing field, your code must always access the property by using the property name. And, if you decide you want to explicitly implement one of the accessor methods, then you must explicitly implement both accessor methods and you are not using the AIP feature anymore. For a single property, the AIP feature is an all-or-nothing deal.

 

Defining Properties Intelligently

Personally, I don’t like properties and I wish that they were not supported in the Microsoft .NET Framework and its programming languages. The reason is that properties look like fields, but they are methods. This has been known to cause a phenomenal amount of confusion. When a program- mer sees code that appears to be accessing a field, there are many assumptions that the programmer makes that may not be true for a property. For example:

■ A property may be read-only or write-only; field access is always readable and writable. If you define a property, it is best to offer both get and set accessor methods.

■ A property method may throw an exception; field access never throws an exception.

 

■ A property cannot be passed as an out or ref parameter to a method; a field can. For ex- ample, the following code will not compile.

 

using System;

 

public sealed class SomeType { private static String Name {


get { return null; } set {}

}

 

static void MethodWithOutParam(out String n) { n = null; }

 

public static void Main() {

// For the line of code below, the C# compiler emits the following:

// error CS0206: A property, indexer or dynamic member access may not

// be passed as an out or ref parameter MethodWithOutParam(out Name);

}

}

 

■ A property method can take a long time to execute; field access always completes immediately.

A common reason to use properties is to perform thread synchronization, which can stop the thread forever, and therefore, a property should not be used if thread synchronization is required. In that situation, a method is preferred. Also, if your class can be accessed remotely

(for example, your class is derived from System.MarshalByRefObject), calling the property method will be very slow, and therefore, a method is preferred to a property. In my opinion, classes derived from MarshalByRefObject should never use properties.

■ If called multiple times in a row, a property method may return a different value each time; a field returns the same value each time. The System.DateTime class has a read-only Now property that returns the current date and time. Each time you query this property, it will

return a different value. This is a mistake, and Microsoft wishes that they could fix the class by making Now a method instead of a property. Environment’s TickCount property is another example of this mistake.

■ A property method may cause observable side effects; field access never does. In other words, a user of a type should be able to set various properties defined by a type in any order he or she chooses without noticing any different behavior in the type.

■ A property method may require additional memory or return a reference to something that is not actually part of the object’s state, so modifying the returned object has no effect on the original object; querying a field always returns a reference to an object that is guaranteed to be part of the original object’s state. Working with a property that returns a copy can be very confusing to developers, and this characteristic is frequently not documented.

It has come to my attention that people use properties far more often than they should. If you examine this list of differences between properties and fields, you’ll see that there are very few cir- cumstances in which defining a property is actually useful and will not cause confusion for develop- ers. The only thing that properties buy you is some simplified syntax; there is no performance benefit compared to calling a non-property method, and understandability of the code is reduced. If I had been involved in the design of the .NET Framework and compilers, I would have not offered proper- ties at all; instead, I would have programmers actually implement GetXxx and SetXxx methods as desired. Then, if compilers wanted to offer some special, simplified syntax for calling these methods, so be it. But I’d want the compiler to use syntax that is different from field access syntax so that pro- grammers really understand what they are doing—a method call.


Properties and the Visual Studio Debugger

Microsoft Visual Studio allows you to enter an object’s property in the debugger’s watch win- dow. When you do this, every time you hit a breakpoint, the debugger calls into the property’s get accessor method and displays the returned value. This can be quite helpful in tracking down bugs, but it can also cause bugs to occur and hurt your debugging performance. For example, let’s say that you have created a FileStream for a file on a network share and then you add FileStream’s Length property to the debugger’s watch window. Now, every time you hit a breakpoint, the debugger will call Length’s get accessor method, which internally makes a network request to the server to get the current length of the file!

Similarly, if your property’s get accessor method has a side effect, then this side effect will execute every time you hit a breakpoint. For example, let’s say that your property’s get acces- sor method increments a counter every time it is called; this counter will now be incremented every time you hit a breakpoint, too. Because of these potential problems, Visual Studio allows you to turn off property evaluation for properties shown in watch windows. To turn property evaluation off in Visual Studio, select Tools, Options, Debugging, and General and in the list box in Figure 10-1, and clear the Enable Property Evaluation And Other Implicit Function Calls check box. Note that even with this item cleared, you can add the property to the watch window and manually force Visual Studio to evaluate it by clicking the force evaluation circle in the watch window’s Value column.

 

 

FIGURE 10-1The Visual Studio General Debugger settings.


Object and Collection Initializers

It is very common to construct an object and then set some of the object’s public properties (or fields). To simplify this common programming pattern, the C# language supports a special object initialization syntax. The following is an example.

 

Employee e = new Employee() { Name = "Jeff", Age = 45 };

 

With this one statement, I am constructing an Employee object, calling its parameterless construc- tor, and then setting its public Name property to "Jeff" and its public Age property to 45. In fact, the preceding code is identical to the following, which you could verify by examining the Intermedi- ate Language (IL) for both of these code fragments.

 

Employee _tempVar = new Employee();

_tempVar.Name = "Jeff";

_tempVar.Age = 45;

 

// Only assign to e if the assignments above don't throw an exception.

// This prevents e from referring to a partially initialized object. Employee e = _tempVar;

 

The real benefit of the object initializer syntax is that it allows you to code in an expression context (as opposed to a statement context), permitting composability of functions, which in turn increases code readability. For example, I can now write the following.

 

String s = new Employee() { Name = "Jeff", Age = 45 }.ToString().ToUpper();

 

So now, in one statement, I have constructed an Employee object, called its constructor, initialized two public properties, and then, using the resulting expression, called ToString on it followed by calling ToUpper. For more about composability of functions, see the “Extension Methods” section in Chapter 8, “Methods.”

As a small side note, C# also lets you omit the parentheses before the open brace if you want to call a parameterless constructor. The following line produces the same IL as the preceding line.

 

String s = new Employee { Name = “Jeff”, Age = 45 }.ToString().ToUpper();

 

If a property’s type implements the IEnumerable or IEnumerable<T> interface, then the prop- erty is considered to be a collection, and initializing a collection is an additive operation as opposed to a replacement operation. For example, suppose I have the following class definition.

 

public sealed class Classroom {

private List<String> m_students = new List<String>(); public List<String> Students { get { return m_students; } }

 

public Classroom() {}

}


I can now have code that constructs a Classroom object and initializes the Students collection as follows.

 

public static void M() {

Classroom classroom = new Classroom {

Students = { "Jeff", "Kristin", "Aidan", "Grant" }

};

 

// Show the 4 students in the classroom foreach (var student in classroom.Students)

Console.WriteLine(student);

}

 

When compiling this code, the compiler sees that the Students property is of type List<String> and that this type implements the IEnumerable<String> interface. Now, the compiler assumes that the List<String> type offers a method called Add (because most collection classes actually offer an Add method that adds items to the collection). The compiler then generates code to call the collec- tion’s Add method. So, the preceding code is converted by the compiler into the following.

 

public static void M() {

Classroom classroom = new Classroom(); classroom.Students.Add("Jeff"); classroom.Students.Add("Kristin"); classroom.Students.Add("Aidan"); classroom.Students.Add("Grant");

 

// Show the 4 students in the classroom foreach (var student in classroom.Students)

Console.WriteLine(student);

}

 

If the property’s type implements IEnumerable or IEnumerable<T> but the type doesn’t offer an Add method, then the compiler does not let you use the collection initialize syntax to add items to the collection; instead, the compiler issues something like the following message: error CS0117: 'System.Collections.Generic.IEnumerable<string>' does not contain a definition for 'Add'.

 

Some collection’s Add methods take multiple arguments—for example, Dictionary’s Add method.

 

public void Add(TKey key, TValue value);

 

You can pass multiple arguments to an Add method by using nested braces in a collection initial- izer, as follows.

 

var table = new Dictionary<String, Int32> {

{ "Jeffrey", 1 }, { "Kristin", 2 }, { "Aidan", 3 }, { "Grant", 4 }

};


The preceding line is identical to the following.

 

var table = new Dictionary<String, Int32>(); table.Add("Jeffrey", 1);

table.Add("Kristin", 2);

table.Add("Aidan", 3);

table.Add("Grant", 4);

 

Anonymous Types

C#’s anonymous type feature allows you to automatically declare an immutable tuple type by using a very simple and succinct syntax. A tuple type is a type that contains a collection of properties that are usually related to each other in some way.1 In the top line of the following code, I am defining a class with two properties (Name of type String, and Year of type Int32), constructing an instance of this type, and setting its Name property to "Jeff" and its Year property to 1964.

 

// Define a type, construct an instance of it, & initialize its properties var o1 = new { Name = "Jeff", Year = 1964 };

 

// Display the properties on the console:

Console.WriteLine("Name={0}, Year={1}", o1.Name, o1.Year);// Displays: Name=Jeff, Year=1964

 

This top line of code creates an anonymous type because I did not specify a type name after the new keyword, so the compiler will create a type name for me automatically and not tell me what it is (which is why it is called an anonymous type). The line of code uses the object initializer syntax dis- cussed in the previous section to declare the properties and also to initialize these properties. Also, because I (the developer) do not know the name of the type at compile time, I do not know what type to declare the variable o1 as. However, this is not a problem, because I can use C#’s implicitly typed local variable feature (var), as discussed in Chapter 9, “Parameters,” to have the compiler infer the type from the expression on the right of the assignment operator (=).

Now, let’s focus on what the compiler is actually doing. When you write a line of code like this:

 

var o = new { property1 = expression1, ..., propertyN = expressionN };

 

the compiler infers the type of each expression, creates private fields of these inferred types, creates public read-only properties for each of the fields, and creates a constructor that accepts all these expressions. The constructor’s code initializes the private read-only fields from the expression results passed in to it. In addition, the compiler overrides Object’s Equals, GetHashCode, and ToString methods and generates code inside all these methods. In effect, the class that the compiler generates looks like the following.

 

[CompilerGenerated]

internal sealed class <>f AnonymousType0<...>: Object { private readonly t1 f1;

public t1 p1 { get { return f1; } }

 

...

 

 

 
 

1 The term originated as an abstraction of the sequence: single, double, triple, quadruple, quintuple, n-tuple.


private readonly tn fn;

public tn pn { get { return fn; } }

 

public <>f AnonymousType0<...>(t1 a1, ..., tn an) { f1 = a1; ...; fn = an; // Set all fields

}

 

public override Boolean Equals(Object value) {

// Return false if any fields don't match; else true

}

 

public override Int32 GetHashCode() {

// Returns a hash code generated from each fields' hash code

}

 

public override String ToString() {

// Return comma­separated set of property name = value pairs

}

}

 

The compiler generates Equals and GetHashCode methods so that instances of the anonymous type can be placed in a hash table collection. The properties are readonly as opposed to read/write to help prevent the object’s hashcode from changing. Changing the hashcode for an object used as a key in a hashtable can prevent the object from being found. The compiler generates the ToString

method to help with debugging. In the Visual Studio debugger, you can place the mouse cursor over a variable that refers to an instance of an anonymous type, and Visual Studio will invoke the ToString method and show the resulting string in a datatip window. By the way, Visual Studio’s IntelliSense will suggest the property names as you write code in the editor—a very nice feature.

The compiler supports two additional syntaxes for declaring a property inside an anonymous type where it can infer the property names and types from variables.

 

String Name = "Grant"; DateTime dt = DateTime.Now;

 

// Anonymous type with two properties

// 1. String Name property set to Grant

// 2. Int32 Year property set to the year inside the dt var o2 = new { Name, dt.Year };

 

In this example, the compiler determines that the first property should be called Name. Because Name is the name of a local variable, the compiler sets the type of the property to be the same type as the local variable: String. For the second property, the compiler uses the name of the field/prop- erty: Year. Year is an Int32 property of the DateTime class, and therefore the Year property in the anonymous type will also be an Int32. Now, when the compiler constructs an instance of this anony- mous type, it will set the instance’s Name property to the same value that is in the Name local variable so the Name property will refer to the same "Grant" string. The compiler will set the instance’s Year property to the same value that is returned from dt’s Year property.


The compiler is very intelligent about defining anonymous types. If the compiler sees that you are defining multiple anonymous types in your source code that have the identical structure, the compiler will create just one definition for the anonymous type and create multiple instances of that type. By “same structure,” I mean that the anonymous types have the same type and name for each property and that these properties are specified in the same order. In the preceding code examples, the type of variable o1 and the type of variable o2 will be the same type because the two lines of code are defin- ing an anonymous type with a Name/String property and a Year/Int32 property, and Name comes before Year.

Because the two variables are of the same type, we get to do some cool things, such as checking whether the two objects contain equal values and assigning a reference to one object into the other’s variable, as follows.

 

// One type allows equality and assignment operations. Console.WriteLine("Objects are equal: " + o1.Equals(o2)); o1 = o2; // Assignment

 

Also, because of this type identity, we can create an implicitly typed array (discussed in the “Initial- izing Array Elements” section in Chapter 16, “Arrays”) of anonymous types.

 

// This works because all of the objects are of the same anonymous type var people = new[] {

o1, // From earlier in this section new { Name = "Kristin", Year = 1970 }, new { Name = "Aidan", Year = 2003 }, new { Name = "Grant", Year = 2008 }

};

 

// This shows how to walk through the array of anonymous types (var is required) foreach (var person in people)

Console.WriteLine("Person={0}, Year={1}", person.Name, person.Year);

 

Anonymous types are most commonly used with the Language Integrated Query (LINQ) tech- nology, where you perform a query that results in a collection of objects that are all of the same anonymous type. Then, you process the objects in the resulting collection. All this takes place in the same method. Here is an example that returns all the files in my document directory that have been modified within the past seven days.

 

String myDocuments = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); var query =

from pathname in Directory.GetFiles(myDocuments) let LastWriteTime = File.GetLastWriteTime(pathname)

where LastWriteTime > (DateTime.Now ­ TimeSpan.FromDays(7)) orderby LastWriteTime

select new { Path = pathname, LastWriteTime };// Set of anonymous type objects

 

foreach (var file in query)

Console.WriteLine("LastWriteTime={0}, Path={1}", file.LastWriteTime, file.Path);


Instances of anonymous types are not supposed to leak outside of a method. A method cannot be prototyped as accepting a parameter of an anonymous type because there is no way to specify the anonymous type. Similarly, a method cannot indicate that it returns a reference to an anonymous type. Although it is possible to treat an instance of an anonymous type as an Object (because all anonymous types are derived from Object), there is no way to cast a variable of type Object back into an anonymous type because you don’t know the name of the anonymous type at compile time.

If you want to pass a tuple around, then you should consider using the System.Tuple type discussed in the next section.

 


Date: 2016-03-03; view: 637


<== previous page | next page ==>
Nbsp;   Passing a Variable Number of Arguments to a Method | TheSystem.Tuple Type
doclecture.net - lectures - 2014-2024 year. Copyright infringement or personal data (0.03 sec.)