Introduce DataBase,Asp.net,JavaScript,Xml,Html,Css,Sql,Php,ASP.NET Controls,AJAX,Tools,HTML,CSS,JavaScript,Open Source Project,WPF,.Net Framework,Linq
Top Recommended Hosting

Use Properties Instead of Accessible Data Members

by the3factory 3/9/2008 7:59:00 AM

The C# language promoted properties from an ad-hoc convention to a first-class language feature. If you're still creating public variables in your types, stop now. If you're still creating get and set methods by hand, stop now. Properties let you expose data members as part of your public interface and still provide the encapsulation you want in an object-oriented environment. Properties are language elements that are accessed as though they are data members, but they are implemented as methods.

Some members of a type really are best represented as data: the name of a customer, the x,y location of a point, or last year's revenue. Properties enable you to create an interface that acts like data access but still has all the benefits of a function. Client code accesses properties as though they are accessing public variables. But the actual implementation uses methods, in which you define the behavior of property accessors.

The .NET Framework assumes that you'll use properties for your public data members. In fact, the data binding code classes in the .NET Framework support properties, not public data members. Data binding ties a property of an object to a user interface control, either a web control or a Windows Forms control. The data binding mechanism uses reflection to find a named property in a type:

textBoxCity.DataBindings.Add( "Text",
address, "City" );

The previous code binds the Text property of the textBoxCity control to the City property of the address object.  It will not work with a public data member named City; the Framework Class Library designers did not support that practice. Public data members are bad practice, so support for them was not added. Their decision simply gives you yet another reason to follow the proper object-oriented techniques. Let me add a quick note for the grizzled C++ and Java programmers: The data binding code does not look for get and set functions, either. You should be using properties instead of the convention of get_ and set_ functions in those languages.

Yes, data binding applies only to those classes that contain elements that are displayed in your user interface logic. But that doesn't mean properties should be used exclusively in UI logic. You should still be using properties for other classes and structures. Properties are far easier to change as you discover new requirements or behaviors over time. You might soon decide that your customer type should never have a blank name. If you used a public property for Name, that's easy to fix in one location:

public class Customer
{
private string _name;
public string Name
{
get
{
return _name;
}
set
{
if (( value == null ) ||
( value.Length == 0 ))
throw new ArgumentException( "Name cannot be blank",
"Name" );
_name = value;
}
}
// ...
}

If you had used public data members, you're stuck looking for every bit of code that sets a customer's name and fixing it there. That takes more timemuch more time.

Because properties are implemented with methods, adding multithreaded support is easier. Simply enhance the implementation of the get and set methods to provide synchronized access to the data:

public string Name
{
get
{
lock( this )
{
return _name;
}
}
set
{
lock( this )
{
_name = value;
}
}
}

Properties have all the language features of methods. Properties can be virtual:

public class Customer
{
private string _name;
public virtual string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
// remaining implementation omitted
}

It's also easy to see that you can extend properties to be abstract or even part of an interface definition:

public interface INameValuePair
{
object Name
{
get;
}
object Value
{
get;
set;
}
}

Last, but certainly not least, you can use interfaces to create const and nonconst versions of an interface:

public interface IConstNameValuePair
{
object Name
{
get;
}
object Value
{
get;
}
}
public interface INameValuePair
{
object Value
{
get;
set;
}
}
// Usage:
public class Stuff : IConstNameValuePair, INameValuePair
{
private string _name;
private object _value;
#region IConstNameValuePair Members
public object Name
{
get
{
return _name;
}
}
object IConstNameValuePair.Value
{
get
{
return _value;
}
}
#endregion
#region INameValuePair Members
public object Value
{
get
{
return _value;
}
set
{
_value = value;
}
}
#endregion
}

Properties are full-fledged, first-class language elements that are an extension of methods that access or modify internal data. Anything you can do with member functions, you can do with properties.

The accessors for a property are two separate methods that get compiled into your type. You can specify different accessibility modifiers to the get and set accessors in a property in C# 2.0. This gives you even greater control over the visibility of those data elements you expose as properties:

// Legal C# 2.0:
public class Customer
{
private string _name;
public virtual string Name
{
get
{
return _name;
}
protected set
{
_name = value;
}
}
// remaining implementation omitted
}

The property syntax extends beyond simple data fields. If your type should contain indexed items as part of its interface, you can use indexers (which are parameterized properties). It's a useful way to create a property that returns the items in a sequence:

public int this [ int index ]
{
get
{
return _theValues [ index ] ;
}
set
{
_theValues[ index ] = value;
}
}
// Accessing an indexer:
int val = MyObject[ i ];

Indexers have all the same language support as single-item properties: They are implemented as methods you write, so you can apply any verification or computation inside the indexer. Indexers can be virtual or abstract, can be declared in interfaces, and can be read-only or read-write. Single-dimension indexers with numeric parameters can participate in data binding. Other indexers can use noninteger parameters to define maps and dictionaries:

public Address this [ string name ]
{
get
{
return _theValues[ name ] ;
}
set
{
_theValues[ name ] = value;
}
}

In keeping with the multidimensional arrays in C#, you can create multidimensional indexers, with similar or different types on each axis:

public int this [ int x, int y ]
{
get
{
return ComputeValue( x, y );
}
}
public int this[ int x, string name ]
{
get
{
return ComputeValue( x, name );
}
}

Notice that all indexers are declared with the this keyword. You cannot name an indexer. Therefore, you can have, at most, one indexer with the same parameter list in each type.

This property functionality is all well and good, and it's a nice improvement. But you might still be tempted to create an initial implementation using data members and then replace the data members with properties later when you need one of those benefits. That sounds like a reasonable strategybut it's wrong. Consider this portion of a class definition:

// using public data members, bad practice:
public class Customer
{
public string Name;
// remaining implementation omitted
}

It describes a customer, with a name. You can get or set the name using the familiar member notation:

string name = customerOne.Name;
customerOne.Name = "This Company, Inc.";

That's simple and straightforward. You are thinking that you could later replace the Name data member with a property, and the code would keep working without any change. Well, that's sort of true.

Properties are meant to look like data members when accessed. That's the purpose behind the new syntax. But properties are not data. A property access generates different MSIL than a data access. The previous customer type generates the following MSIL for the Name field:

.field public string Name

Accessing the field generates these statements:

ldloc.0
ldfld      string NameSpace.Customer::Name
stloc.1

Storing a value in the field generates this:

ldloc.0
ldstr      "This Company, Inc."
stfld      string NameSpace.Customer::Name

Don't worrywe're not going to look at IL all day. But here, it is important because we are about to see how changing between data members and properties breaks binary compatibility. Consider this version of the customer type, which is created by using properties:

public class Customer
{
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
// remaining implementation omitted
}

When you write C# code, you access the name property using the exact same syntax:

string name = customerOne.Name;
customerOne.Name = "This Company, Inc.";

But the C# compiler generates completely different MSIL for this version. The Customer type has this:

.property instance string Name()
{
.get instance string NameSpace.Customer::get_Name()
.set instance void NameSpace.Customer::set_Name(string)
} // end of property Customer::Name
.method public hidebysig specialname instance string
get_Name() cil managed
{
// Code size       11 (0xb)
.maxstack  1
.locals init ([0] string CS$00000003$00000000)
IL_0000:  ldarg.0
IL_0001:  ldfld      string NameSpace.Customer::_name
IL_0006:  stloc.0
IL_0007:  br.s       IL_0009
IL_0009:  ldloc.0
IL_000a:  ret
} // end of method Customer::get_Name
.method public hidebysig specialname instance void
set_Name(string 'value') cil managed
{
// Code size       8 (0x8)
.maxstack  2
IL_0000:  ldarg.0
IL_0001:  ldarg.1
IL_0002:  stfld      string NameSpace.Customer::_name
IL_0007:  ret
} // end of method Customer::set_Name

Two major points must be understood about how property definitions translate into MSIL. First, the .property directive defines the type of the property and the functions that implement the get and set accessors of the property. The two functions are marked with hidebysig and specialname. For our purposes, those designations mean that these functions are not called directly in C# source code, and they are not to be considered part of the formal type definition. Instead, you access them through the property.

Sure, you expected different MSIL to be generated for the property definition. More important to this discussion, there are changes to the MSIL generated for the get and set access to the property as well:

// get
ldloc.0
callvirt   instance string NameSpace.Customer::get_Name()
stloc.1
// set
ldloc.0
ldstr      "This Company, Inc."
callvirt   instance void NameSpace.Customer::set_Name(string)

The same C# source to access the name of a customer compiles to very different MSIL instructions, depending on whether the Name member is a property or a data member. Accessing a property and accessing a data member use the same C# source. It's the work of the C# compiler to translate the source into different IL necessary for properties or data members.

Although properties and data members are source compatible, they are not binary compatible. In the obvious case, this means that when you change from a public data member to the equivalent public property, you must recompile all code that uses the public data member.While looking at the IL for a property, you probably wonder about the relative performance of properties and data members. Properties will not be faster than data member access, but they might not be any slower. The JIT compiler does inline some method calls, including property accessors. When the JIT compiler does inline property accessors, the performance of data members and properties is the same. Even when a property accessor has not been inlined, the actual performance difference is the negligible cost of one function call. That is measurable only in a small number of situations.

Whenever you expose data in your type's public or protected interfaces, use properties. Use an indexer for sequences or dictionaries. All data members should be private, without exception. You immediately get support for data binding, and you make it much easier to make any changes to the implementation of the methods in the future. The extra typing to encapsulate any variable in a property amounts to one or two minutes of your day. Finding that you need to use properties later to correctly express your designs will take hours. Spend a little time now, and save yourself lots of time later.

Related posts

Sign up for PayPal and start accepting credit card payments instantly.


Powered by BlogEngine.NET 1.2.0.0