Department of InformatiX
Microsoft .NET Micro Framework Tools & Resources

In order to keep the footprint small, not every possible C# feature is supported by .NET Micro Framework. However, some require only small trick to make them working (no, generics are not the case). I thought it might come handy to write it all down at one place.

First, a table.

People seem to like tables.   —Raymond

Feature Required C# Compiler .NET Micro Framework Required Framework Features
Partial classes 2.0 yes
Static classes 2.0 yes
Aliases 2.0 yes
Property access modifiers 2.0 yes
Anonymous methods 2.0 yes
Delegate inference 2.0 yes
Variable capturing 2.0 yes
Yields 2.0 yes IEnumerable, IEnumerator
Generics 2.0 no generics
Nullables 2.0 no generics, Nullable<>
Null coalescing operator 2.0 yes
Feature Required C# Compiler .NET Micro Framework Required Framework Features
Auto-implemented properties3.0 yes
Object initializers 3.0 yes
Collection initializers 3.0 yes IEnumerable
Implicitly typed local variables3.0 yes
Implicitly typed arrays 3.0 yes
Extension methods 3.0 YES ExtensionAttribute
Lambda epressions 3.0 YES (Func<>)
Expression trees 3.0 no generics, Expression<>
Query expressions 3.0 YES IEnumerable, Expressions.*
Anonymous types 3.0 no generics, StringBuilder
Partial methods 3.0 yes
Feature Required C# Compiler .NET Micro Framework Required Framework Features
Dynamic lookup 4.0 no generics, CallSite, Binder
Named parameters 4.0 yes
Optional parameters 4.0 yes
Variance in generic types 4.0 no generics

 

The table contains mix of different features. Some are only syntax sugar, some are deep CLR changes and some require framework libraries support. The point is that all of them require the C# compiler to do some additional stuff or support additional keywords.

If you find out a way how to get any of the red items working, I will update the table – let me know.

C# 2.0 Language Features

Those who remember Visual Studio .NET might know that quite lot of things came with CLR 2.0 and C# 2.0. The most important and complex change was, sure, the introduction of generics, but there were also other important things, now in daily use. Fortunately, most of them work out of the box:

Partial Classes

Purpose: To be able to split one class, struct or interface into several files. Details

Example:

// Banana.Boo.cs public partial class Banana { ... // Boo part } // Banana.Foo.cs public partial class Banana { ... // Foo part }

Causes: The compiler to join the files in single class/struct/interface.

Status: Works without troubles.

Static Classes

Purpose: Class which cannot be instantiated or inherited. Contains only static members and has no instance constructors. Details

Example:

public static class Math { ... // static methods, constants, etc... }

Causes: The compiler to not include a default instance constructor.

Status: Works without troubles.

Aliases

Purpose: To differentiate between types with same name, and to reference the root namespace. Details

Example:

extern alias Foo; extern alias Boo; ... public void DoStuff() { Foo::MyClass.SayHello(); // allows to use identically named types Boo::MyClass.SayHello(); global::System.Diagnostics.Debug.Print("Done."); // assures the correct type is used ::System.Diagnostics.Debug.Print("Done."); // equivalent to global:: }

Causes: Helps compiler to resolve namespaces.

Status: Works without troubles, but the extern alias requires special build parameters to work, so it is not standardly available.

Property Access Modifiers

Purpose: To allow properties different access for read and write. Details

Example:

public string Name { get { ... } private set { ... } }

Causes: The compiler to apply modifiers to the generated set_ and get_ methods.

Status: Works without troubles.

Anonymous Methods

Purpose: To allow inline methods without names. Details

Example:

// public delegate void EventHandler(object sender, EventArgs e); Microsoft.SPOT.Application.Startup += new Microsoft.SPOT.EventHandler( delegate(object sender, EventArgs e) { ... });

Causes: The compiler to generate an instance method with a name of its choice.

Status: Works without troubles.

Delegate Inference

Purpose: No need to instantiate the delegate.

Example:

// public delegate void EventHandler(object sender, EventArgs e); Microsoft.SPOT.Application.Startup += delegate(object sender, EventArgs e) { ... }; // But also (not that much known): Microsoft.SPOT.Application.Startup += MyStartupHandler; ... private void MyStartupHandler(object sender, EventArgs e) { ... }

Causes: The compiler to find out (infer) the required delegate type and convert it to the code above.

Status: Works without troubles.

Delegate Covariance and Contra-variance

Purpose: Provide a degree of flexibility when matching method signatures with delegate types. Details

Example:

// public delegate void InsertEventHandler(object sender, MediaEventArgs e); Microsoft.SPOT.IO.RemovableMedia.Insert += delegate(object sender, EventArgs e) { ... }; // But also: Microsoft.SPOT.IO.RemovableMedia.Insert += delegate { ... }; // equivalent to delegate(object, object)

Causes: The compiler to accept valid delegates.

Status: Works without troubles.

Variable capturing

Purpose: Allow to reference variables out of the anonymous method's scope.

Example:

private void SetLabelText(string text) { if (MyLabel.CheckAccess()) MyLabel.Text = text; else MyLabel.Dispatcher.BeginInvoke(new System.Threading.ThreadStart(delegate { MyLabel.Text = text; })); }

Causes: The compiler to generate nested class for storing the variable. This can get you into troubles, consider the following code:

for (int i = 0; i < 10; i++) { Dispatcher.BeginInvoke(new System.Threading.ThreadStart(delegate { System.Diagnostics.Debug.Print("i = " + i); })); }
Now, read it again: The compiler generates a class to store the variable. So the output will be 10, 10, 10, 10, 10, 10, 10, 10, 10, 10. To get the desired behaviour, you must convince the compiler that the variable is not going to change ever, for example:
for (int i = 0; i < 10; i++) { int iFixed = i; Dispatcher.BeginInvoke(new System.Threading.ThreadStart(delegate { System.Diagnostics.Debug.Print("i = " + iFixed); })); }
Now it is as expected: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9.

Status: Works without troubles.

Yields

Purpose: To simplify enumerable types implementation. Details

Example:

static System.Collections.IEnumerable InfiniteRandomSequence { get { System.Random generator = new System.Random(); while (true) yield return generator.Next(); } }

Causes: The compiler to generate a nested class implementing a state machine.

Status: Works without troubles.

You might also want to check a very nice article in MSDN Magazine Create Elegant Code With Anonymous Methods, Iterators, And Partial Classes for more details on the above features, their implementations and how to effectively use them.

Generics

Purpose: Type safety, code reuse and improved performance. Details

Example:

public class Complex<T> { public T Real; public T Imaginary; }

Causes: In contrast to all features above, this is a fundamental CLR feature. The type substition occurs at runtime, not compile time. Not only because that, generics are not C++ templates.

Status: Currently not available in .NET Micro Framework due to the significant footprint demands.

Nullables

Purpose: Allow value types to have no value, be uninitialized. Details

Example:

int? value = null;

Causes: Substituted by generic types System.Nullable<T>.

Status: Currently not available in .NET Micro Framework, because it requires generics.

Null coalescing operator

Purpose: Simplify code. Details

Example:

string parameter = userValue ?? "default value";

Causes: a ?? b is substituted by a != null ? a : (b).

Status: Works without troubles.

C# 3.0 Language Features

The flagship of C# 3.0 was LINQ (Language-Integrated Query) and almost all of the new language features were introduced to make it possible, or look good. C# 3.0 uses the CLR 2.0, so no changes for .NET Micro Framework.

Auto-Implemented Properties

Purpose: Simplify code. Details

Example:

public string Name { get; set; }

Causes: The compiler to create the backing field automatically with a name of its choice.

Status: Works without troubles.

Object Initializers

Purpose: Allow creation of anonymous types, simplify code. Details

Example:

Microsoft.SPOT.DispatcherTimer timer = new Microsoft.SPOT.DispatcherTimer { Interval = new System.TimeSpan(0, 0, 1), IsEnabled = true }; // constructors with parameters are also supported: Microsoft.SPOT.DispatcherTimer timer = new Microsoft.SPOT.DispatcherTimer(Dispatcher) { Interval = new System.TimeSpan(0, 0, 1), IsEnabled = true };

Causes: The compiler to insert a code which instantiates the required type, sets desired fields or properties, and assigns it to the declared variable.

Status: Works without troubles.

Collection Initializers

Purpose: Allow creation of anonymous types, simplify code. Details

Example:

System.Collections.ArrayList list = new System.Collections.ArrayList { 25, 3, 85, "MF", null };

Causes: The compiler to prepend a code which instantiates the required type, calls its Add methods, and assigns it to the declared variable.

Status: Works without troubles. The object being initialized is of course required to have a public Add method accepting single parameter, public parameterless constructor, and, not sure why, inherit the System.Collections.IEnumerable interface, even unimplemented.

Implicitly Typed Local Variables

Purpose: Allow storage of anonymous types, simplify code writing. Details

Example:

var mf = "Small is beautiful.";

Causes: The compiler to substitute the correct type. At compile time. Not runtime.

Status: Works without troubles.

Implicitly Typed Arrays

Purpose: Allow storage of anonymous types, simplify code writing. Details

Example:

object array = new[] { "alfa", "beta" }; // also note this works since C# 1.0 (array initializer): string[] array = { "alfa", "beta" };

Causes: The compiler to insert the correct type.

Status: Works without troubles.

Extension Methods

Purpose: Add instance functionality to a type you cannot control, in a syntax easy way, allows query expressions. Details

Example:

static class RandomExtensions { public static byte NextByte(this System.Random random) { return (byte)random.Next(byte.MaxValue); } } ... System.Random generator = new System.Random(); byte randomByte = generator.NextByte(); // equivalent to RandomExtensions.NextByte(generator);

Causes: The compiler to replace the extension calls with respective static calls.

Status: Works, but the compiler needs to mark the extension methods and their class using System.Runtime.CompilerServices.ExtensionAttribute, which is not present in the standard .NET Micro Framework libraries. It is easy to supply one, though:

using System; namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)] public sealed class ExtensionAttribute : Attribute { } }

Lambda Expressions

Purpose: Easy syntax for short anonymous methods, allows selections and conditions in query expressions. Details

Example:

// parameters and return value delegate bool Filter(int x); ... Filter isZero = a => a == 0; bool twentyFiveIsZero = isZero(25); // no parameters delegate int Constant(); ... Constant alwaysSix = () => 6; int six = alwaysSix(); // no return value or long body // public delegate void ThreadStart(); new System.Threading.Thread(() => { System.Diagnostics.Debug.Print("Hello World!"); }).Start(); // multiple parameters // public delegate void EventHandler(object sender, EventArgs e); Microsoft.SPOT.Application.Startup += (sender, e) => { System.Diagnostics.Debug.Print("Application started."); };

Causes: Treated as standard anonymous method.

Status: Works, but you need to declare the delegates. System.Func<> types are not available due to lack of generics.

Expression Trees

Purpose: Store method evaluation tree, allows queries to be optimized. Details

Example:

System.Linq.Expressions.Expression<System.Func<int, int>> lessOne = x => x - 1;

Causes: The compiler to insert plenty of code constructing the tree using types in System.Linq.Expressions namespace.

Status: Not available due to lack of generics (and all the tree classes). You could declare the non-generic classes yourself, but you won't be able to tell the compiler to use them.

Query Expressions

Purpose: Integrate natural queries into language. Details

Example:

System.Collections.IEnumerable shortA = from s in new string[] { "anna", "robert", "alexandra", "alvy" } where s[0] == 'a' orderby s.Length descending select s.ToUpper(); // equivalent to couple of extension methods and lambdas: System.Collections.IEnumerable shortA = new string[] { "anna", "robert", "alexandra", "alvy" }).Where(x => x[0] == 'a').OrderByDescending(y => y.Length).Select(z => z.ToUpper());

Causes: The compiler to convert the query into extension methods and lambda functions, resp. static method calls and anonymous functions.

Status: Works, but you need to declare all the extension methods. See Mobile Software Engineering blog for an example of implementation.

Anonymous Types

Purpose: Encapsulate set of read-only properties without the need to declare a type, used in queries selection. Details

Example:

object friend = new { Name = "குமார்", Age = 12 };

Causes: The compiler to generate the class for you, properties are read-only (underlaying fields readonly).

Status: Not available, unfortunately the current C# requires generics in the generated class (and a System.Text.StringBuilder).

Partial Methods

Purpose: Allow generated code to be regenerated without affecting developer's code, used in LINQ to SQL Designer. Details

Example:

// Window1.μg.cs - generated code public partial class Window1 { partial void OnComponentsInitialized(); public Window1() { InitializeComponents(); OnComponentsInitialized(); } ... } // Window1.cs - developer's code public partial class Window1 { partial void OnComponentsInitialized() { // your constructor code goes here } }

Causes: The compiler to merge the methods if an implementation exists, otherwise completely remove it including all calls to it.

Status: Works without troubles.

C# 4.0 Language Features

And for the fourth version, the flagship was the Dynamic Language Runtime I guess, which is of course not present in .NET Micro Framework. In the world of .NET Framework, it is needed to support some dynamic languages, e.g. IronPython, and to simplify the COM interopability. Neither of this is needed on .NET Micro Framework.

Dynamic Lookup

Purpose: Bypass static type checking and resolve object members at runtime. Details

Example:

dynamic expando = new System.Dynamic.ExpandoObject(); expando.MyFunnyProperty = "Yabadabadoo";

Causes: Nested site container class is generated and calls to a binder are used to manipulate the object.

Status: The Dynamic Language Runtime is not available on .NET Micro Framework (and the generated code uses generics).

Named Parameters

Purpose: Eliminate the need to remember or lookup parameters order. Details

Example:

Microsoft.SPOT.Net.NetworkInformation.NetworkInterface.EnableStaticIP(gatewayAddress: "88.86.110.254", ipAddress: "88.86.110.84", subnetMask = "255.255.255.0");

Causes: The compiler to match the parameters and reorder them correctly.

Status: Works without troubles.

Optional Parameters

Purpose: Specify default values of method parameters to be able to omit them during calls. Details

Example:

public void PrintLines(string line, int count = 2) { for (int i = 0; i < count; i++) System.Diagnostics.Debug.Print(line); } ... PrintLines("Hello World!"); // Prints out 2 lines PrintLines("Hello World!", 5); // Prints out 5 lines

Causes: The C# compiler substitutes missing parameters with their defaut values. At compile time. This means that if the default value is changed, callers are not updated until recompilation.

Status: Works without troubles. The System.Runtime.InteropServices.OptionalAttribute and DefaultParameterValueAttribute need not to be specified, this is a CLR feature.

Variance in Generic Types

Purpose: Allow implicit conversion of generic types. Details

Example:

System.Collections.Generic.IEnumerable<object> objects = new System.Collections.Generic.List<string>();

Causes: The compiler to allow and runtime to consider implicit conversions of type parameters when working with generics.

Status: Generics are not available.

Comments
Sign in using Live ID to be able to post comments.