Create .NET Properties in Eiffel

by Paul Bates (modified: 2007 Mar 15)

.NET properties are a fundamental part of the CLR and CTS. They are used in a plethora of ways by Microsoft, ISVs and your other developers. For a number of users .NET properties are just a syntactical sugar unifying a getter function and setter routine, and opening up access to encapsulated internal fields. Other common uses of properties provide a means validate client input on a setter and raising an exception if the input does not conform, or supporting performance scenarios such as lazy evaluation and caching.

Note: Although DbC, in the case of validating client input, can been seen as a replacement for the exception model it's not the be all and end all when working with .NET. When you release your assembly it typically will not contain contracts, unless you specifically release a "debug" version also (It is not recommended to release only a debug version because the contracts bulk code size and decrease performance.) When using your assembly from another language those contracts will not be checked, It is therefore important to raise the correct exception when necessary. When your working to interop with the .NET world you should be working using .NET rules, conventions and other practices.

Properties open up the whole new world of development scenarios, from creating Windows Forms/WPF controls, web services, true reflection based applications and much, much more. EiffelEnvision is now written in Eiffel for .NET and makes heavy use of properties. EiffelEnvision's integration into MsBuild would be an impossibility without Eiffel for .NET properties.

Getting Started

The Eiffel language (what is refer to as "classic" Eiffel) has not been changed to support Eiffel for .NET. With the classic version of the language remaining the same, Eiffel Software had to provide extension points to support Eiffel for .NET, which is done through using the indexing clauses (Soon to be note due to ECMA conformance.) It is actually possible to write a .NET application using just Eiffel libraries and compile it upon a Linux installation, using classic code generation. The vice versa is also equally possible

Now lets get on with examples. Take the following snippet of Eiffel code:

deferred class TEXT_BASE feature -- Access text: SYSTEM_STRING -- Actual text representation invariant not_text_is_empty: not {SYSTEM_STRING}.is_null_or_empty (text) end

The examples introduces the class TEXT_BASE, for all descendants whom wish to expose a representation in a textual form. In Eiffel clients of TEXT_BASE have access to text, by default, because text is implicitly exported to ANY.

When compiled for .NET the implementation class for TEXT_BASE with contain a .NET field with the name "$$text" returning a System.String. The name "$$text" is a mangled name and is not intended for direct use (Implementation types are not meant to be used from other languages and contain many implementation specifics that have no valuable used except to the internals of the assembly generated.)

Note: The .NET framework does not support multiple inheritance and probably will not for a very long time, if ever. Eiffel for .NET on the other hand is Eiffel so it does support multiple inheritance. To support multiple inheritance an Eiffel class is represented as three different .NET type entities in a generated assembly. There is; an implementation class, an interface and a factory type used for instantiating Eiffel types from other languages. I'm not going to go into details of how this works because that is an article in itself. As noted, this is not a beginner article.

Consumers of an assembly written in Eiffel for .NET have access to text through a function call defined on TEXT_BASE's generated interface. It is a function call because .NET interfaces prohibit field definitions for obvious reasons. A function call is not the most respected way to retrieve an object attribute as it implies some logical computation to arrive at the result and, in many cases, indicates that an object reference that is return will differ on subsequent calls. What is really needed is a property!

Through the use of indexing and an Eiffel for .NET specific indexing term, property, a property can be defined on a generated Eiffel class' interface.

text: SYSTEM_STRING -- Actual text representation indexing property: auto end

In C#, a consuming project would be able to use

// C# code for retrieving the value of a TEXT_BASE's text property. public string GetText (TEXT_BASE tb) { return tb.text; }

Note that text has been augmented with an indexing clauses and property term. The reserved property term value auto is used to instruct the compiler to automatically generate a property name based on the Eiffel attribute name. Alternatively a different name can be supplied for the property for external consumers of the generated assembly. To accomplish this, instead of specify auto specify an identifier name as an Eiffel string:

text: SYSTEM_STRING -- Actual text representation indexing property: "my_text_property" end

C# consumer clients will then have to use the my_text_property

// C# code for retrieving the value of a TEXT_BASE's text property. public string GetText (TEXT_BASE tb) { return tb.my_text_property; // The following comment code will not compile if uncommented as the property text does not exist. // return tb.text; }

That's it, properties in Eiffel for .NET.

But Wait... There's More

So far you've only seen read-only property. Any attempt to set the property will result in a compilation error, because there is no associated property setter. Eiffel for .NET properties do not magical inherit new semantics to support setting without explicitly defining those semantics. To do so would break one of the core principles of Eiffel, allowing external the modification of a object's state.

The new assigner mechanism defined in the ECMA standard give way to the solution for creating a .NET property that is both read/write.

deferred class TEXT_BASE feature -- Access text: SYSTEM_STRING assign set_text -- Actual text representation indexing property: auto end feature -- Element change set_text (a_text: like text) -- Set `text' with `a_text' require not_a_text_is_empty: not {SYSTEM_STRING}.is_null_or_empty (a_text) do text := a_text ensure text_set: {SYSTEM_STRING}.equals (text, a_text) end invariant not_text_is_empty: not {SYSTEM_STRING}.is_null_or_empty (text) end

With the text attribute mark with and assigner (using the assign keyword), C# users will be able to do the following:

// C# code for setting a TEXT_BASE's text property. public void SetText (TEXT_BASE tb, string text) { if (tb == null) throw new ArgumentException ("tb"); if (string.IsNullOrEmpty (text)) throw new ArgumentException ("text"); // Valid because the generated .NET property 'text' has a setter tb.text = "Hello World"; Debug.Assert (text == tb.text); }

In addition, due to the declaration of an assigner, Eiffel clients of TEXT_BASE will also be able to use a similar code style convention:

feature -- Basic operations set_text (tb: TEXT_BASE; text: SYSTEM_STRING) require tb_attached: tb /= Void not_text_is_empty: not {SYSTEM_STRING}.is_null_or_empty (text) do -- Valid because the the Eiffel attribute `text' has the assigner `set_text'. tb.text := text ensure text_set: {SYSTEM_STRING}.equals (text, tb.text) end

In both cases for C# and Eiffel the resulting call to the above assignment is always a call to TEXT_BASE.set_text. text will never be set directly.

What Next?

In a move to make your Eiffel assemblies look and feel more like other assemblies created in other languages, I'll be publishing future articles that discuss "Single Types" and .NET constructors in Eiffel. Using a compound of these features allows consumers of Eiffel for .NET assemblies to use a more standard way to instantiating type and accessing members. I'll guide you through the simple steps it takes to convert code used with Eiffel assemblies:

// Using Eiffel from C# GREETING t = Create.GREETING.make ("Hello"); t.set_name ("Paul"); t.say_hello();


// Using a single type in Eiffel from C# Greeting t = new Greeting ("Hello"); t.Name = "Paul"; t.SayHello();