Export (0) Print
Expand All
8 out of 8 rated this helpful - Rate this topic

Properties in J#

Visual Studio .NET 2003
 

Pratap Lakshman
Visual J# .NET Team
Microsoft Corporation

May 2004

Applies to:
   Microsoft®Visual J#®.NET
   Microsoft® Visual Studio®.NET

Summary: Learn to choose whether to use a property or a method for a particular need in your J# applications. Make sure your application's properties express your desired semantic intent. (8 printed pages)

Contents

Introduction
Capturing Programmer Intent
Properties as "Smart Fields"
Indexers as "Smart Arrays"
Conclusion
References

Introduction

A 'Property' is some named data value of a component, which is accessible through so-called accessor methods. Properties typically affect the appearance or behavior of the component. For example, a button might have a property named "text" that represents the text displayed in the button.

Property accessors are usually composed of a getter and setter method. Properties may be defined with only a getter or only a setter; such properties are therefore read-only or write-only. If both accessors are defined, the property allows both reading and writing.

This separate specification of accessors is a common programming idiom. Microsoft® Visual Basic® 6.0 [1] programmers had accessors in the form of the Set, Get, and Let statements. CLOS [2] has the notion of accessors in the form of readers and writers—a reader being a function that reads a slot, and a writer being a function that writes a slot. The structure definition facility in Scheme [3] expands into a series of generated definitions, including a getter and a setter for each field within the structure. The Java language [4] has its own notion of properties based on a naming pattern for the accessors.

Properties show up in a number of ways in object-oriented systems:

  • Properties may be exposed in scripting environments as though they were fields of objects.
  • Properties can be accessed programmatically by other components calling their getter and setter methods.
  • Properties may be presented in a property sheet for a user to edit as part of the process of customizing a component.
  • Properties can be persisted, so that their state will be stored away as part of the persistent state of the component.

Capturing Programmer Intent

The Common Language Runtime (CLR) formalizes this programming idiom through the CTS. The CTS [5] defines four kinds of members in a class: fields, properties, methods, and events. Fields and properties provide the mechanism to associate named data values with classes or with object instances. Like a field, a property has a name and a type; unlike a field, a property has no allocated storage. A property is a special metadata component. The JIT compiler and the execution engine are completely unaware of it. There are no IL instructions dealing with a property.

Properties allow types to expose data whose values can be retrieved and set via arbitrary code rather than via direct memory access. Metadata associated with the type is used to indicate which methods are to be used in this fashion. Every used occurrence of a property name in a source program is encoded as a conventional call to the associated method in IL. Properties are more than syntactic sugar, though. They capture the semantic intent of the programmer. This leads to more consistent APIs and better development tools.

Properties as "Smart Fields"

In J#, the setter and getter accessors are specified as independent methods. The accessors may be static or virtual. They may be members of a class, or on an interface. They may work by simply getting or setting the value of an associated private backing field, or may compute the value on the fly.

Briefly, here are the rules to declare a property in J# [6]:

  • The accessibility of the accessors must be identical.
  • The accessors must all be static or all virtual.
  • The type of the property must be the return type of the getter, and the type of the last argument of the setter.
  • The accessors must adhere to a specific naming pattern, as shown below.
  • Accessors may be overloaded and overridden, as long as language rules are not broken.

The declaration of each accessor is decorated with the /** @property */ tag. The method name should begin with either set_ or get_, followed by the actual name of the property (as visible to the user). This naming convention and separate specification of methods reflects the underlying CLR support for properties and the CLS naming convention [5].

Consider an implementation of the button class (in J#) that exposes a "text" property:

public class button
{
  private String s;

  /** @property */
  public void set_text(String val) { s = val; }

  /** @property */
  public String get_text() { return s; }
}

Here is the relevant IL when the above class is compiled to a library.

.property instance string text()
{
  .get instance string button::get_text()
  .set instance void button::set_text(string)
} // end of property button::text

The IL describes the property metadata [7], the key pieces of which are as follows:

  • The signature of the property is defined by its name (in this case "text"), the return type (in this case string), and the sequence of parameters (in this case, none).
  • The accessors are associated with the semantics of the property: .set for the setter, .get for the getter.

J# does not provide any special syntax for accessing properties. The only way to access the property is through invoking the associated accessors using the conventional method-call syntax. Java-language programmers do not have to learn any new syntax to access properties.

Languages like C#, however, provide field-like access to properties. Consider consuming this class from a C# program:

public class app
{
  public static void Main(string [] args)
  {
    button b = new button();
    b.text = "Ok";
    string s = b.text;
    System.Console.WriteLine(s);
  }
}

Here is the relevant IL (along with the corresponding lines in the source code):

//000006:     b.text = "Ok";
  IL_0006:  ldloc.0
  IL_0007:  ldstr      "Ok"
  IL_000c:  callvirt   instance void    
            [button]button::set_text(string)
//000007:     string s = b.text;
  IL_0011:  ldloc.0
  IL_0012:  callvirt   instance string 
            [button]button::get_text()
  IL_0017:  stloc.1

Notice that every used occurrence of a property name in the source program is encoded as a conventional call to the associated accessor in IL.

Since all access to the property must pass through the accessors, properties provide a mechanism for enforcing invariants on values. For example, if some checking code is put inside a setter, it is guaranteed that no code may change the value without passing the check. Similarly, if instrumentation code is placed inside the accessors, it is guaranteed that all accesses to the value will be intercepted.

Indexers as "Smart Arrays"

An indexer (or indexed property) takes parameters other than the standard value parameter of the setter. Languages may choose to provide special syntax for consuming indexers—typically, similar to array indexing.

J# supports only one indexed property per type, and it must be marked as the default member of the type using the System.Reflection.DefaultMember attribute. All other rules that apply to defining simple properties apply to indexed properties as well.

Consider an implementation of a TextFile class (in J#) that exposes an indexed property to enable expressions of the form:

file[pos]

to read and write individual characters to a disk file. When the indexing appears on the left side of an assignment expression, a character is written to the corresponding position in the file:

file[pos] = value;

When the indexing is on the right side of an assignment, the value of the corresponding character is read from the file.

value = file[pos];

Here is the TextFile class:

import java.io.*;
/** @attribute System.Reflection.DefaultMember("at") */
public class TextFile
{
  private RandomAccessFile _raf;
  public TextFile(String filename, String mode)
  {
    try {
      _raf = new RandomAccessFile(filename, mode);
    } catch (Exception e) { ; }
  }
  /** @property */
  public int get_at(int pos)
  {
    int ch = -1;
    try {
      _raf.seek(pos);
      ch = _raf.read();
    } catch (Exception e) { ; }
    return ch;
  }
  /** @property */
  public void set_at(int pos, int ch)
  {
    try {
      _raf.seek(pos);
      _raf.write(ch);
    } catch (Exception e) { ; }
  }
}

As previously mentioned, languages may choose to provide special syntax for consuming properties. C# provides array-like access to indexed properties. Here is a C# example that prints the contents of the file 'sample.txt' to the console:

public class app
{
  public static void Main(string [] args)
  {
    TextFile f = new TextFile("sample.txt", "r");
    int c = -1;
    int i = 0;
    while ((c = f[i++]) != -1)
    {
      System.Console.Write((char) c);
    }
  }
}

The syntax is natural; an array of characters is a common abstract model for a text file. The only operation needed is indexing, and the indexing is consistent and complete.

To see the indexing in action, consider a program (again in C#) that declares two TextFile objects associated with a pair of files:

  TextFile source = new TextFile("sample.txt", "r");
  TextFile dest = new TextFile("example.txt", "rw");
  char ch;
  int i, j;

ch is assigned the jth character of the source file by:

  ch = (char) source[j];

In a language that does not provide special syntax for accessing indexed properties (such as J#), the assignment could have been written as follows:

  ch = (char) source.get_at(j); // J# example

If the indexing appears on the left side of an assignment the effect must be different—the character in the file must be written, not read. Consider an assignment in which the TextFile indexing is on the left side:

  dest[i] = ch;    // C# example

In a language that does not provide special syntax for accessing indexed properties, the assignment could have been written as follows:

  dest.set_at(i, ch);    // J# example

Not only can indexing be used on either side of an assignment, it can be used on both sides of a single assignment to read from one file and write to another:

  dest[i] = source[j];    // C# example

The indexing is therefore self-consistent. If the indexing could appear only once in an expression the effect would be unnatural, since there is no such prohibition on the indexing of regular arrays. The assignment expression that indexes on both sides is equivalent to the following:

  dest.set_at(i, source.get_at(j));    // J# example

TextFile indexing can also be used naturally in other contexts. Suppose that characters have to be read from one file, converted by a translation table, and written to another file. A simple translation table can be represented as an array:

  int [] translate;  // say of some given size.

Each character from the input file is mapped through the table and into the output file. The necessary mapping is accomplished by an assignment:

  dest[i] = translate[ source[j] ];    // C# example

This expression naturally combines TextFile indexing with normal array indexing; the two interact as expected.

As already seen, an indexed property takes parameters other than the standard value parameter of the setter. More than one parameter can be used as the index (multi-index property). The index parameter need not be restricted to an integer; it could be a String, or any other type for that matter. Similarly, the getter could return just about any type. This makes indexed properties well suited for implementing associative arrays.

Conclusion

Properties represent logical data and should be defined to mimic conventional usage.

Languages like C# provide convenient syntax to access properties. Properties must not be chosen purely for their lexical appeal, however, as the notational convenience comes at a cost, and the expense of accessing a property must not outweigh this notational convenience. That every used occurrence of a property name in a source program is encoded as a conventional call to the associated method in IL will not be apparent to the casual reader of source code, leading to subtle problems that are difficult to discover. It is for this reason that J# does not provide any special syntax for accessing properties.

There is some taste involved in choosing if a property or a method is more appropriate for a particular need. When chosen well, properties serve as a powerful abstraction to express the semantic intent of the programmer.

References:

[1] Visual Basic for Applications Reference, MSDN 2004

[2] Guy L. Steele Jr., Common Lisp, the Language 2e, Digital Press, 1990

[3] Dybvig, Kent R., The Scheme Programming Language 2e, Prentice Hall, 1996.

[4] Arnold, K., Gosling, J., The Java Programming Language 2e, Addison Wesley, 1998.

[5] Common Language Infrastructure (CLI) Partition I - Architecture, MSDN 2004.

[6] Defining and Using Properties, Visual J# Reference, MSDN 2004.

[7] Common Language Infrastructure (CLI) Partition II: Metadata Definition and Semantics, MSDN, 2004.

Did you find this helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft. All rights reserved.