Sunday, December 9, 2007

Features of C# 2.0

The list of .NET 2.0 and C# 2.0 new features is extracted from the appendix of the book Practical .NET2 and C#2. All mentioned features are thoroughly covered in the book.
Contents
• Assembly
• Application localization
• Application build process
• Application configuration
• Application deployment
• CLR
• Delegate
• Threading/Synchronization
• Security
• Reflection/Attribute
• Interoperability
• C# 2.0
• Exceptions
• Collections
• Debugging
• Base classes
• IO
• Windows Forms 2.0
• ADO.NET 2.0
• ADO.NET 2.0: SQL Server data provider (SqlClient)
• XML
• .NET Remoting
• ASP.NET 2.0
• Web Services
Assembly
The use of the AssemblyKeyFile attribute to sign an assembly is to be avoided. It is now preferred that you use the /keycontainer and /keyfile options of csc.exe, or the new project properties of Visual Studio 2005.
The new System.Runtime.CompilerServices.InternalsVisibleToAttribute attribute allows you to specify assemblies which have access to non-public types within the assembly to which you apply the attribute (kind of 'assemblies friendship').
The ildasm.exe 2.0 tool offers, by default, the possibility of obtaining statistics in regards to the byte size of each section of an assembly and the display of its metadata. With ildasm.exe 1.x, you needed to use the /adv command line option.
Application localization
The resgen.exe tool can now generate C# or VB.NET code which encapsulates access to resources in a strongly typed manner.
Application build process
The .NET platform is now delivered with a new tool called msbuild.exe. This tool is used to build .NET applications and is used by Visual Studio 2005, but you can use it to launch your own build scripts.
Application configuration
The .NET 2.0 platform features a new, strong typed management of your configuration parameters. Visual Studio 2005 also contains a configuration parameter editor which generates the code needed to take advantage of this feature.
Application deployment
The new deployment technology named ClickOnce allows a fine management of the security, updates, as well as on-demand installation of applications. Visual Studio 2005 offers some practical facilities to take advantage of this technology.
CLR
A major bug with version 1.x of the CLR which made it possible to modify signed assemblies has been addressed in version 2.
The System.GC class offers two new methods named AddMemoryPressure() and RemoveMemoryPressure() which allow you to give the GC an indication in regards to the amount of unmanaged memory held. Another method CollectionCount(int generation) allows you to know the number of collections applied to the specified generation.
New features have been added to the ngen.exe tool to support assemblies using reflection, and to automate the update of the compiled version of an assembly when one of its dependencies has changed.
The ICLRRuntimeHost interface used from unmanaged code to host the CLR replaces the ICorRuntimeHost interface. It allows access to a new API permitting the CLR to delegate a certain number of core responsibilities such as the loading of assemblies, thread management, or the management of memory allocations. This API is currently only used by the runtime host for SQL Server 2005.
Three new mechanisms named Constrained Execution Region (CER), Critical Finalizer, and Critical Region (CR) allow advanced developers to increase the reliability of applications such SQL Server 2005 which are likely to deal with a shortage of system resources.
A memory gate mechanism can be used to evaluate, before an operation, if sufficient memory is available.
You can now quickly terminate a process by calling the FailFast() static method which is part of the System.Environment class. This method bypasses certain precautions such as the execution of finalizers or the pending finally blocks.
Delegate
A delegate can now reference a generic method or a method that is part of a generic type. We then see appearing the notion of generic delegates.
With the new overloads of the Delegate.CreateDelegate(Type, Object, MethodInfo) method, it is now possible to reference a static method and its first argument from a delegate. The calls to the delegates then do not need this first argument and is similar to the use of instance method calls.
In addition, the invocation of methods through the use of delegates is now more efficient.
Threading/Synchronization
You can easily pass information to a new thread that you created by using the new ParametrizedThreadStart delegate. Also, new constructors of the Thread class allow you to set the maximum size of the thread stack in bytes.
The Interlocked class offers new methods and allows to deal with more types such as IntPtr or double.
The WaitHandle class offers a new static method named SignalAndWait(). In addition, all classes deriving from WaitHandle offer a new static method named OpenExisting().
The EventWaitHandle can be used instead of its subclasses AutoResetEvent and ManualResetEvent. In addition, it allows to name an event and thus share it amongst multiple processes.
The new class Semaphore allows you take advantage of Win32 semaphores from your managed code.
The new method SetMaxThreads() of the ThreadPool class allows to modify the maximal number of threads within the CLR thread pool from managed code.
The .NET 2.0 framework offers new classes which allow to capture and propagate the execution context of the current thread to another thread.
Security
The System.Security.Policy.Gac class allows the representation of a new type of evidence based on the presence of an assembly in the GAC.
The following new permission classes have been added: System.Security.Permissions.KeyContainerPermission, System.Net.NetworkInformation.NetworkInformationPermission, System.Security.Permissions.DataProtectionPermission, System.Net.Mail.SmtpPermission, System.Data.SqlClient.SqlNotificationPermission, System.Security.Permissions.StorePermission, System.Configuration.UserSettingsPermission, System.Transactions.DistributedTransactionPermission, and System.Security.Permissions.GacIdentityPermission.
The IsolatedStorageFile class presents the following new methods: GetUserStoreForApplication(), GetMachineStoreForAssembly(), GetMachineStoreForDomain(), and GetMachineStoreForApplication().
The .NET 2.0 framework allows to launch a child process within a different security context than the parent process.
The .NET 2.0 framework offers new types within the System.Security.Principal namespace allowing the representation and manipulation of Windows security identifiers.
The .NET 2.0 framework presents new types within the System.Security.AccessControl namespace to manipulate Windows access control settings.
The .NET 2.0 framework offers new hashing methods within the System.Security.Cryptography namespace.
The .NET 2.0 framework offers several classes giving access to the functionality offered by the Windows Data Protection API (DAPI).
The System.Configuration.Configuration class allows the easy management of the application configuration file. In particular, you can use it to encrypt your configuration data.
The .NET 2.0 framework offers new types within the System.Security.Cryptography.X509Certificates and System.Security.Cryptography.Pkcs namespaces which are specialized for the manipulation of X.509 and CMS/Pkcs7 certificates.
The new namespace named System.Net.Security offers the new classes SslStream and NegociateStream which allow the use of the SSL, NTLM, and Kerberos security protocols to secure data streams.
Reflection/Attribute
You now have the possibility of loading an assembly in reflection-only mode. Also, the AppDomain class offers a new event named ReflectionOnlyAssemblyResolve triggered when the resolution of an assembly fails in the reflection-only context.
The .NET 2.0 framework introduces the notion of conditional attribute. Such an attribute has the particularity of being taken into consideration by the C# 2 compiler only when a certain symbol is defined.
Interoperability
The notion of function pointers and delegates are now interchangeable using the new GetDelegateForFunctionPointer() and GetFunctionPointerForDelegate() methods of the Marshal class.
The HandleCollector class allows you to supply to the garbage collector an estimate on the number of Windows handles currently held.
The new SafeHandle and CriticalHandle classes allow to harness Windows handles more safely than with the IntPtr class.
The tlbimp.exe and tlbexp.exe tools present a new option named /tlbreference which allow the explicit definition of a type library without having to go through the registry. This allows the creation of compilation environments which are less fragile.
Visual Studio 2005 offers features to take advantage of the reg-free COM technology of Windows XP within a .NET application. This technology allows the use of a COM class without needing to register it into the registry.
Structures related to COM technology such as BINDPTR, ELEMDESC, and STATDATA have been moved from the System.Runtime.InteropServices namespace to the new System.Runtime.InteropServices.ComTypes namespace. This namespace contains new interfaces which redefine certain standard COM interfaces such as IAdviseSink or IConnectionPoint.
The new namespace named System.Runtime.InteropServices contains new interfaces such as _Type, _MemberInfo, or _ConstructorInfo which allow unmanaged code to have access to reflection services. Of course, the related managed classes (Type, MemberInfo, ConstructorInfo...) implement these interfaces.
C# 2.0
Undoubtedly, the highlight feature in .NET 2.0 and C# 2.0 is generics.
C# 2.0 allows the declaration of anonymous methods (which can be seen as closures).
C# 2.0 presents a new syntax to define iterators.
The csc.exe compiler offers the following new options /keycontainer, /keyfile, /delaysign, /errorreport and /langversion.
C# 2.0 brings forth the notions of namespace alias qualifier, of global:: qualifier, and of external alias to avoid certain identifier conflicts.
C# 2.0 introduces the new compiler directives #pragma warning disable and #pragma warning restore.
The C# 2.0 compiler is now capable of inferring a delegation type during the creation of a delegate object. This makes source code more readable.
The .NET 2.0 framework introduces the notion of nullable types which can be exploited through a special C# 2.0 syntax.
C# 2.0 now allows you to spread the definition of a type across multiple source files within the same module. This new feature is called partial type.
C# 2.0 allows the assignment of a different visibility to the accessor of a property or indexer.
C# 2.0 allows the definition of static classes.
C# 2.0 now allows the definition of a table field with a fixed number of primitive elements within a structure.
Visual Studio 2005 intellisense feature now uses the XML information contained within /// comments.
Visual Studio 2005 allows you to build UML-like class diagrams in-sync with your code.
Exceptions
The SecurityException class and Visual Studio 2005 have been improved to allow you to more easily test and debug your mobile code.
The Visual Studio 2005 debugger offers a practical wizard to obtain a complete set of information relating to an exception.
Visual Studio 2005 allows you to be notified when a problematic event known by the CLR occurs. These events sometime provoke managed exceptions.
Collections
The whole set of collection types within the .NET framework have been revised in order to account for generic types. Here is a comparison chart between the System.Collections and System.Collections.Generic namespaces.
System.Collections.Generics System.Collections
Comparer Comparer
Dictionary HashTable
List LinkedList ArrayList
Queue Queue
SortedDictionary SortedList SortedList
Stack Stack
ICollection ICollection
IComparable System.IComparable
IComparer IComparer
IDictionary IDictionary
IEnumerable IEnumerable
IEnumerator IEnumerator
IList IList
The System.Array class has no generic equivalent and is still current. Indeed, since the beginning of .NET, the collection model proposed by this class supports a certain level of genericity. It presents new methods such as void Resize(ref T[] array, int newSize), void ConstrainedCopy(...), and IList AsReadOnly(T[] array).
Debugging
The System.Diagnostics namespace provides new attributes DebuggerDisplayAttribute, DebuggerBrowsable, DebuggerTypeProxyAttribute, and DebuggerVisualizerAttribute which allow you to customize the display of the state of your objects while debugging.
.NET 2.0 allows indicating through attributes the assemblies, modules, or zones of code that you do not wish to debug. This feature is known as Just My Code.
C# 2.0 programmers now have access to the Edit and Continue feature allowing them to modify their code while debugging it.
.NET 2.0 presents the new enumeration named DebuggableAttribute.DebuggingModes which is a set of binary flags on the debugging modes we wish to use.
Base classes
The primitive types (integer, boolean, floating point numbers) now expose a method named TryParse() which allows to parse a value within a string without raising an exception in the case of failure.
The .NET 2.0 framework offers several implementations derived from the System.StringComparer abstract class which allows to compare strings in a culture and case sensitive manner.
The new Sytem.Diagnostics.Stopwatch class is provided especially to accurately measure elapsed time.
The new DriveInfo class allows the representation and manipulation of volumes.
The .NET 2.0 framework introduces the notion of trace source allowing a better management of traces. Also, the following trace listener classes have been added: ConsoleTraceListener, DelimitedListTraceListener, XmlWriterTraceListener, and WebPageTraceListener.
Several new functionalities have been added to the System.Console class in order to improve data display.
IO
The .NET 2.0 framework offers the new class System.Net.HttpListener which allows to take advantage of the HTTP.SYS component of Windows XP SP2 and Windows Server 2003 to develop a HTTP server.
In .NET 2.0, the classes that are part of the System.Web.Mail namespace are now obsolete. To send mail, you must use the classes within the System.Net.Mail namespace. This new namespace now contains classes to support the MIME standard.
New methods now allow you to read and write a file in a single call.
New classes are now available to compress/decompress a data stream.
A new unmanaged version System.IO.UnmanagedMemoryStream of the MemoryStream class allows you to avoid copying of data onto the CLR’s object heap and is thus more efficient.
The new System.Net.FtpWebRequest class implements a FTP client.
The new namespace System.Net.NetworkInformation contains types which allow to query the network interfaces available on a machine in order to know their states, their traffic statistics, and to be notified on state changes.
Web resource caching services are now available in the new System.Net.Cache namespace.
The new System.IO.Ports.SerialPort class allows the use of a serial port in a synchronous or event based manner.
Windows Forms 2.0
Visual Studio 2005 takes advantage of the notion of partial classes in the management of Windows Forms. Hence, it will not mix anymore the generated code with our own code in the same file.
Windows Forms 2.0 offers the BackgroundWorker class which standardizes the development of asynchronous operations within a form.
The appearance (i.e. the visual style) of controls is better managed by Windows Forms 2.0 as it does not need to use the comctl32.dll DLL to obtain a Windows XP style.
Windows Forms 2.0 and Visual Studio 2005 contain the framework and development tools for a quick and easy development of presentation and edition windows for data.
Windows Forms 2.0 presents the new classes BufferedGraphicsContext and BufferedGraphics which allow a fine control on a double buffering mechanism.
The ToolStrip, MenuStrip, StatusStrip, and ContextMenuStrip controls: these controls, respectively, replace the ToolBar, MainMenu, StatusBar, and ContextMenu controls (which are still present for backward compatibility reasons). In addition to nicer visual style, these new controls are particularly easy to manipulate during the design of a window, thanks to a consistent API. New functionality has been added such as the possibility of sharing a render between controls, the support for animated GIFs, opacity, transparency, and the facility of saving the current state (position, size…) in the configuration file. The hierarchy of the classes derived from the class System.Windows.Forms.ToolStripItem constitutes as many elements which can be inserted in this type of control.
The DataGridView and BindingNavigator controls: these controls are part of a new framework to develop data driven forms. This framework is the subject of the Viewing and editing data section a little later in this chapter. Know that it is now preferable to use a DataGridView for the display of any data table or list of objects instead of the Windows Forms 1.0 DataGrid control.
The FlowLayoutPanel and TableLayout controls: these controls allow the dynamic positioning of the child controls that it contains when the user modifies its size. The layout philosophy of the FlowLayoutPanel control is to list the child controls horizontally or vertically in a way where they are moved when the control is resized. This approach is similar to what we see when we resize an HTML document displayed by a browser. The layout philosophy of the TablePanel control is comparable to the anchoring mechanism where the child controls are resized based on the size of the parent control. However, here the child controls are found in the cells of a table.
The SplitterPanel and SplitContainer controls: the combined use of these controls allows the easy implementation of splitting of a window in a way that it can be resized, as we had with version 1.1 using the Splitter control.
The WebBrowser control: this control allows the insertion of a web browser directly in a Windows Forms form.
The MaskedTextBox control: this control displays a TextBox in which the format of the text to insert is constrained. Several types of masks are offered by default, such as dates or US telephone number. Of course, you can also provide your own masks.
The SoundPlayer and SystemSounds controls: the SoundPlayer class allows you to play sounds in .wav format while the SystemSounds class allows you to retrieve the system sounds associated with the current user of the operating system.
ADO.NET 2.0
ADO.NET 2.0 presents new abstract classes such as DbConnection or DbCommand in the new namespace System.Data.Common which implements the IDbConnection or IDbCommand interfaces. The use of these new classes is now preferred to the use of the interfaces.
ADO.NET 2.0 offers an evolved architecture of abstract factory classes which allow decoupling the data access code from the underlying data provider.
ADO.NET 2.0 presents new features to construct connection strings independently of the underlying data provider.
ADO.NET 2.0 offers a framework allowing the programmatic traversal of a RDBMS schema.
The indexing engine used internally by the framework when you use instances of the DataSet and DataTable classes have been revised in order to be more efficient during the loading and manipulation of data.
Instances of the DataSet and DataTable classes are now serializable into a binary form using the new SerializationFormat RemotingFormat{get;set;} property. You can achieve a gain of 3 to 8 times in relation to the use of XML serialization.
The DataTable class is now less dependant on the DataSet class as the XML features of this one have been added.
The new method DataTable DataView.ToTable() allows the construction of a DataTable containing a copy of a view.
ADO.NET 2.0 now offers a bridge between the connected and disconnected modes which allow the DataSet/DataTable and DataReader classes to work together.
Typed DataSets directly take into account the notion of relationships between tables. Now, thanks to partial types, the generated code is separated from your own code. Finally, the new notion of TableAdapter allows you to create some sort of typed SQL requests directly usable from your code.
ADO.NET 2.0 allows to store data updates in a more efficient manner, thanks to batch updates.
ADO.NET 2.0: SQL Server data provider (SqlClient)
You now have the possibility of enumerating SQL Server data sources.
You have more control on connection pooling.
The SqlClient data provider of ADO.NET 2.0 allows the execution of commands in an asynchronous way.
You can harness the bulk copy services of the SQL Server tool bcp.exe using the SqlBulkCopy class.
You can obtain statistics about the activity of a connection.
There is a simplified and freely distributed version of SQL Server 2005 which offers several advantages over the previous MSDE and Jet products.
Transaction
The new namespace named System.Transactions (contained in the Systems.Transactions.dll) offers, at the same time, a unified transactional programming model and a new transactional engine which has the advantage of being extremely efficient on certain types of lightweight transactions.
XML
The performance of all classes involved in XML data handling have been significantly improved (by a factor of 2 to 4 in classic use scenarios according to Microsoft).
The new System.Xml.XmlReaderSettings class allows to specify the type of verifications which must be done when using a subclass of XmlReader to read XML data.
It is now possible to partially validate a DOM tree loaded within an instance of XmlDocument.
It is now possible to modify a DOM tree stored in an XmlDocument instance through the XPathNavigator cursor API.
The XslCompiledTransform class replaces the XslTransform class which is now obsolete. Its main advantage is in compiling XSLT programs into MSIL code before applying a transformation. According to Microsoft, this new implementation improves performance by a factor of 3 to 4. Moreover, Visual Studio 2005 can now debug XSLT programs.
Support for the XML DataSet class has been improved. You can now load XSD schemas with names repeated in different namespaces, and load XML data containing multiple schemas. Also, XML load and save methods have been added to the DataTable class.
The 2005 version of SQL Server brings forth new features in regards to the integration of XML data inside a relational database.
XML serialization can now serialize nullable information and generic instances. Also, a new tool named sgen.exe allows the pre-generation of an assembly containing the code to serialize a type.
.NET Remoting
The new IpcChannel channel is dedicated to the communication between different processes on a same machine. Its implementation is based on the notion of Windows named pipe.
If you use a channel of type TCP, you now have the possibility of using the NTLM and Kerberos protocols to authenticate the Windows user under which the client executes, to encrypt the exchanged data and impersonate your requests.
New attributes of the System.Runtime.Serialization namespace allow the management of problems inherent to the evolution of a serializable class.
It is possible to consume an instance of a closed generic type, with the .NET Remoting technology, whether you are in CAO or WKO mode.
ASP.NET 2.0
Visual Studio .NET 2005 is now supplied with a web server which allows the testing and debugging of your web applications during development.
It is now easy to use the HTTP.SYS component to build a web server which hosts ASP.NET without needing to use IIS.
ASP.NET 2.0 presents a new model for the construction of classes representing web pages. This model is based on partial classes, and is different than the one offered in ASP.NET 1.x.
The CodeBehind directive of ASP.NET v1.x is no longer supported.
In ASP.NET 2.0, the model used for dynamic compilation of your web application has significantly improved, and is now based on several new standard folders. In addition, ASP.NET 2.0 offers two new pre-compilation modes: the in-place pre-compilation, and the deployment pre-compilation.
To counter the effects of large viewstates in ASP.NET 1.x, ASP.NET 2.0 stores information in a base64 string, more efficiently, and introduces the notion of control-state.
ASP.NET 2.0 introduces a new technique which allows to postback a page to another page.
Certain events have been added to the lifecycle of a page.
ASP.NET 2.0 offers an infrastructure to allow the process of the same request across multiple threads of a pool. This allows us to avoid running out of threads within the pool when several long requests are executed at the same time.
New events have been added to the HttpApplication class.
The manipulation of configuration files has been simplified because of the Visual Studio 2005 intellisense, a new web interface, a new UI integrated in IIS, and because of new base classes.
ASP.NET 2.0 offers a framework allowing the standard management of events occurring during the life of a web application.
You can now configure ASP.NET 2.0 so that it can detect whether it is possible to store a session identifier in a client-side cookie, or if it should automatically switch over to the URI mode if cookies are not supported.
ASP.NET 2.0 now allows you to supply your own session or session ID management mechanism.
The cache engine of ASP.NET 2.0 offers interesting new features. You can now use the VaryByControl sub-directive in your pages. You can substitute dynamic fragments within your cached pages. You can associate your cached data dependencies towards tables and rows of a SQL Server data source. Finally, you can create your own types of dependencies.
ASP.NET 2.0 offers new server controls allowing declarative binding to a data source.
ASP.NET 2.0 offers a new hierarchy of server-side controls for the presentation and the edition of data. These controls have the peculiarity of being able to use a data source control to read and write data.
ASP.NET 2.0 offers a simplified template syntax.
ASP.NET 2.0 adds the notion of master pages which allows the easy reuse of a page design across all pages of a website.
ASP.NET 2.0 now offers an extensible architecture to allow insertion of navigational controls within your site.
With ASP.NET 2.0, you can use the Forms authentication mode without being forced to use cookies.
ASP.NET 2.0 allow the management of user authentication data as well of the roles to which they may belong, through the use of a database. Hence, several new server-side controls have been added to greatly simplify the development of ASP.NET applications which support authentication.
ASP.NET 2.0 presents a new framework allowing the storage and access of users' profiles.
ASP.NET 2.0 offers a framework facilitating the management and maintenance of the overall appearance of a site, thanks to the notions of themes and skins.
ASP.NET 2.0 also offers a framework dedicated to the creation of web portals through the use of what is called WebParts.
ASP.NET 2.0 offers a framework allowing the modification of rendered HTML code if the initiating HTTP request comes from a system with a small screen such as a mobile phone. Concretely, the rendering of each server control is done in a way to use less screen space. This modification is done through the use of adapter objects which are requested automatically and implicitly by ASP.NET during the rendering of the page. The "Inside the ASP.NET Mobile Controls" article on MSDN offers a good starting point on this new ASP.NET 2.0 feature.
Web Services
The proxy classes generated by wsdl.exe now offers a new asynchronous model which allows cancellation.
Create Elegant Code with Anonymous Methods, Iterators, and Partial Classes


Contents
Iterators
Iterator Implementation
Recursive Iterations
Partial Types
Anonymous Methods
Passing Parameters to Anonymous Methods
Anonymous Method Implementation
Generic Anonymous Methods
Anonymous Method Example
Delegate Inference
Property and Index Visibility
Static Classes
Global Namespace Qualifier
Inline Warning
Conclusion
Sidebars
What are Generics?



Fans of the C# language will find much to like in Visual C#® 2005. Visual Studio® 2005 brings a wealth of exciting new features to Visual C# 2005, such as generics, iterators, partial classes, and anonymous methods. While generics is the most talked-about and anticipated feature, especially among C++ developers who are familiar with templates, the other new features are important additions to your Microsoft® .NET development arsenal as well. These features and language additions will improve your overall productivity compared to the first version of C#, leaving you to write cleaner code faster. For some background information on generics, you should take a look at the sidebar "What are Generics?"
Iterators
In C# 1.1, you can iterate over data structures such as arrays and collections using a foreach loop:
string[] cities = {"New York","Paris","London"};
foreach(string city in cities) { Console.WriteLine(city); }
In fact, you can use any custom data collection in the foreach loop, as long as that collection type implements a GetEnumerator method that returns an IEnumerator interface. Usually you do this by implementing the IEnumerable interface:
public interface IEnumerable { IEnumerator GetEnumerator(); }
public interface IEnumerator { object Current{get;}
bool MoveNext();
void Reset();
}
Often, the class that is used to iterate over a collection by implementing IEnumerable is provided as a nested class of the collection type to be iterated. This iterator type maintains the state of the iteration. A nested class is often better as an enumerator because it has access to all the private members of its containing class. This is, of course, the Iterator design pattern, which shields iterating clients from the actual implementation details of the underlying data structure, enabling the use of the same client-side iteration logic over multiple data structures, as shown in Figure 1.

Figure 1 Iterator Design Pattern
In addition, because each iterator maintains separate iteration state, multiple clients can execute separate concurrent iterations. Data structures such as the Array and the Queue support iteration out of the box by implementing IEnumerable. The code generated in the foreach loop simply obtains an IEnumerator object by calling the class's GetEnumerator method and uses it in a while loop to iterate over the collection by continually calling its MoveNext method and current property. You can use IEnumerator directly (without resorting to a foreach statement) if you need explicit iteration over the collection.
But there are some problems with this approach. The first is that if the collection contains value types, obtaining the items requires boxing and unboxing them because IEnumerator.Current returns an Object. This results in potential performance degradation and increased pressure on the managed heap. Even if the collection contains reference types, you still incur the penalty of the down-casting from Object. While unfamiliar to most developers, in C# 1.0 you can actually implement the iterator pattern for each loop without implementing IEnumerator or IEnumerable. The compiler will choose to call the strongly typed version, avoiding the casting and boxing. The result is that even in version 1.0 it's possible not to incur the performance penalty.
To better formulate this solution and to make it easier to implement, the Microsoft .NET Framework 2.0 defines the generic, type-safe IEnumerable and IEnumerator interfaces in the System.Collections.Generics namespace:
public interface IEnumerable
{ IEnumerator GetEnumerator(); }
public interface IEnumerator : IDisposable
{ ItemType Current{get;}
bool MoveNext();
}
Besides making use of generics, the new interfaces are slightly different than their predecessors. Unlike IEnumerator, IEnumerator derives from IDisposable and does not have a Reset method. The code in Figure 2 shows a simple city collection implementing IEnumerable, and Figure 3 shows how the compiler uses that interface when spanning the code of the foreach loop. The implementation in Figure 2 uses a nested class called MyEnumerator, which accepts as a construction parameter a reference back to the collection to be enumerated. MyEnumerator is intimately aware of the implementation details of the city collection, an array in this example. The MyEnumerator class maintains the current iteration state in the m_Current member variable, which is used as an index into the array.
The second and more difficult problem is implementing the iterator. Although that implementation is straightforward for simple cases (as shown in Figure 3), it is challenging with more advanced data structures, such as binary trees, which require recursive traversal and maintaining iteration state through the recursion. Moreover, if you require various iteration options, such as head-to-tail and tail-to-head on a linked list, the code for the linked list will be bloated with various iterator implementations. This is exactly the problem that C# 2.0 iterators were designed to address. Using iterators, you can have the C# compiler generate the implementation of IEnumerator for you. The C# compiler can automatically generate a nested class to maintain the iteration state. You can use iterators on a generic collection or on a type-specific collection. All you need to do is tell the compiler what to yield in each iteration. As with manually providing an iterator, you need to expose a GetEnumerator method, typically by implementing IEnumerable or IEnumerable.
You tell the compiler what to yield using the new C# yield return statement. For example, here is how you use C# iterators in the city collection instead of the manual implementation of Figure 2:
public class CityCollection : IEnumerable
{
string[] m_Cities = {"New York","Paris","London"};
public IEnumerator GetEnumerator()
{
for(int i = 0; i yield return m_Cities[i];
}
}
You can also use C# iterators on non-generic collections:
public class CityCollection : IEnumerable
{
string[] m_Cities = {"New York","Paris","London"};
public IEnumerator GetEnumerator()
{
for(int i = 0; i yield return m_Cities[i];
}
}
In addition, you can use C# iterators on fully generic collections, as shown in Figure 4. When using a generic collection and iterators, the specific type used for IEnumerable in the foreach loop is known to the compiler from the type used when declaring the collection—a string in this case:
LinkedList list = new LinkedList();
/* Some initialization of list, then */
foreach(string item in list)
{
Trace.WriteLine(item);
}
This is similar to any other derivation from a generic interface.
If for some reason you want to stop the iteration midstream, use the yield break statement. For example, the following iterator will only yield the values 1, 2, and 3:
public IEnumerator GetEnumerator()
{
for(int i = 1;i< 5;i++)
{
yield return i;
if(i > 2)
yield break;
}
}
Your collection can easily expose multiple iterators, each used to traverse the collection differently. For example, to traverse the CityCollection class in reverse order, provide a property of type IEnumerable called Reverse:
public class CityCollection
{
string[] m_Cities = {"New York","Paris","London"};
public IEnumerable Reverse
{
get
{
for(int i=m_Cities.Length-1; i>= 0; i--)
yield return m_Cities[i];
}
}
}
Then use the Reverse property in a foreach loop:
CityCollection collection = new CityCollection();
foreach(string city in collection.Reverse)
{
Trace.WriteLine(city);
}
There are some limitations to where and how you can use the yield return statement. A method or a property that has a yield return statement cannot also contain a return statement because that would improperly break the iteration. You cannot use yield return in an anonymous method, nor can you place a yield return statement inside a try statement with a catch block (and also not inside a catch or a finally block).


Iterator Implementation
The compiler-generated nested class maintains the iteration state. When the iterator is first called in a foreach loop (or in direct iteration code), the compiler-generated code for GetEnumerator creates a new iterator object (an instance of the nested class) with a reset state. Every time the foreach loops and calls the iterator's MoveNext method, it begins execution where the previous yield return statement left off. As long as the foreach loop executes, the iterator maintains its state. However, the iterator object (and its state) does not persist across foreach loops. Consequently, it is safe to call foreach again because you will get a new iterator object to start the new iteration. This is why IEnumerable does not define a Reset method.
But how is the nested iterator class implemented and how does it manage its state? The compiler transforms a standard method into a method that is designed to be called multiple times and that uses a simple state machine to resume execution after the previous yield statement. All you have to do is indicate what and when to yield to the compiler using the yield return statement. The compiler is even smart enough to concatenate multiple yield return statements in the order they appear:
public class CityCollection : IEnumerable
{
public IEnumerator GetEnumerator()
{
yield return "New York";
yield return "Paris";
yield return "London";
}
}
Let's take a look at the GetEnumerator method of the class shown in the following lines of code:
public class MyCollection : IEnumerable
{
public IEnumerator GetEnumerator()
{
//Some iteration code that uses yield return
}
}
When the compiler encounters a class member with a yield return statement such as this, it injects the definition of a nested class called GetEnumerator$__IEnumeratorImpl, as shown in the C# pseudocode in Figure 5. (Remember that all of the features discussed in this article—the names of the compiler-generated classes and fields—are subject to change, in some cases quite drastically. You should not attempt to use reflection to get at those implementation details and expect consistent results.)
The nested class implements the same IEnumerable interface returned from the class member. The compiler replaces the code in the class member with an instantiation of the nested type, assigning to the nested class's member variable a reference back to the collection, similar to the manual implementation shown in Figure 2. The nested class is actually the one providing the implementation of IEnumerator.


Recursive Iterations
Iterators really shine when it comes to iterating recursively over a data structure such as a binary tree or any complex graph of interconnecting nodes. With recursive iteration, it is very difficult to manually implement an iterator, yet using C# iterators it is done with great ease. Consider the binary tree in Figure 6. The full implementation of the tree is part of the source code available with this article.
The binary tree stores items in nodes. Each node holds a value of the generic type T, called Item. Each node has a reference to a node on the left and a reference to a node on the right. Values smaller than Item are stored in the left-side subtree, and larger values are stored in the right-side subtree. The tree also provides an Add method for adding an open-ended array of values of the type T, using the params qualifier:
public void Add(params T[] items);
The tree provides a public property called InOrder of type IEnumerable. InOrder calls the recursive private helper method ScanInOrder, passing to ScanInOrder the root of the tree. ScanInOrder is defined as:
IEnumerable ScanInOrder(Node root);
It returns the implementation of an iterator of the type IEnumerable, which traverses the binary tree in order. The interesting thing about ScanInOrder is the way it uses recursion to iterate over the tree using a foreach loop that accesses the IEnumerable returned from a recursive call. With in-order iteration, every node iterates over its left-side subtree, then over the value in the node itself, then over the right-side subtree. For that, you need three yield return statements. To iterate over the left-side subtree, ScanInOrder uses a foreach loop over the IEnumerable returned from a recursive call that passes the left-side node as a parameter. Once that foreach loop returns, all the left-side subtree nodes have been iterated over and yielded. ScanInOrder then yields the value of the node passed to it as the root of the iteration and performs another recursive call inside a foreach loop, this time on the right-side subtree.
The InOrder property allows you to write the following foreach loop to iterate over the entire tree:
BinaryTree tree = new BinaryTree();
tree.Add(4,6,2,7,5,3,1);
foreach(int num in tree.InOrder)
{
Trace.WriteLine(num);
}
// Traces 1,2,3,4,5,6,7
You can implement pre-order and post-order iterations in a similar manner by adding additional properties.
While the ability to use iterators recursively is obviously a powerful feature, it should be used with care as there can be serious performance implications. Each call to ScanInOrder requires an instantiation of the compiler-generated iterator, so recursively iterating over a deep tree could result in a large number of objects being created behind the scenes. In a balanced binary tree, there are approximately n iterator instantiations, where n is the number of nodes in the tree. At any given moment, approximately log(n) of those objects are live. In a decently sized tree, a large number of those objects will make it past the Generation 0 garbage collection. That said, iterators can still be used to easily iterate over recursive data structures such as trees by using stacks or queues to maintain a list of nodes still to be examined.


Partial Types
C# 1.1 requires you to put all the code for a class in a single file. C# 2.0 allows you to split the definition and implementation of a class or a struct across multiple files. You can put one part of a class in one file and another part of the class in a different file, noting the split by using the new partial keyword. For example, you can put the following code in the file MyClass1.cs:
public partial class MyClass
{
public void Method1()
{...}
}
In the file MyClass2.cs, you can insert this code:
public partial class MyClass
{
public void Method2()
{...}
public int Number;
}
In fact, you can have as many parts as you like in any given class. Partial type support is available for classes, structures, and interfaces, but you cannot have a partial enum definition.
Partial types are a very handy feature. Sometimes it is necessary to modify a machine-generated file, such as a Web service client-side wrapper class. However, changes made to the file will be lost if you regenerate the wrapper class. Using a partial class, you can factor those changes into a separate file. ASP.NET 2.0 uses partial classes for the code-beside class (the evolution of codebehind), storing the machine-generated part of the page separately. Windows® Forms uses partial classes to store the visual designer output of the InitializeComponent method as well as the member controls. Partial types also enable two or more developers to work on the same type while both have their files checked out from source control without interfering with each other.
You may be asking yourself, what if the various parts define contradicting aspects of the class? The answer is simple: a class (or a struct) can have two kinds of aspects or qualities: accumulative and non-accumulative. The accumulative aspects are things that each part of the class can choose to add, such as interface derivation, properties, indexers, methods, and member variables.
For example, the following code shows how a part can add interface derivation and implementation:
public partial class MyClass
{}
public partial class MyClass : IMyInterface
{
public void Method1()
{...}
public void Method2()
{...}
}
The non-accumulative aspects are things that all the parts of a type must agree upon. Whether the type is a class or a struct, type visibility (public or internal) and the base class are non-accumulative aspects. For example, the following code does not compile because not all the parts of MyClass concur on the base class:
public class MyBase
{}
public class SomeOtherClass
{}
public partial class MyClass : MyBase
{}
public partial class MyClass : MyBase
{}
//Does not compile
public partial class MyClass : SomeOtherClass
{}
In addition to having all parts define the same non-accumulative parts, only a single part can override a virtual or an abstract method, and only one part can implement an interface member.
C# 2.0 supports partial types as follows: when the compiler builds the assembly, it combines from the various files the parts of a type and compiles them into a single type in Microsoft intermediate language (MSIL). The generated MSIL has no recollection which part came from which file. Just like in C# 1.1 the MSIL has no record of which file was used to define which type. Also worth noting is that partial types cannot span assemblies, and that a type can refuse to have other parts by omitting the partial qualifier from its definition.
Because all the compiler is doing is accumulating parts, a single file can contain multiple parts, even of the same type, although the usefulness of that is questionable.
In C#, developers often name a file after the class it contains and avoid putting multiple classes in the same file. When using partial types, I recommend indicating in the file name that it contains parts of a type such as MyClassP1.cs, MyClassP2.cs, or employing some other consistent way of externally indicating the content of the source file. For example, the Windows Forms designer stores its portion of the partial class for the form in Form1.cs to a file named Form1.Designer.cs.
Another side effect of partial types is that when approaching an unfamiliar code base, the parts of the type you maintain could be spread all over the project files. In such cases, my advice is to use the Visual Studio Class View because it displays an accumulative view of all the parts of the type and allows you to navigate through the various parts by clicking on its members. The navigation bar provides this functionality as well.


Anonymous Methods
C# supports delegates for invoking one or multiple methods. Delegates provide operators and methods for adding and removing target methods, and are used extensively throughout the .NET Framework for events, callbacks, asynchronous calls, and multithreading. However, you are sometimes forced to create a class or a method just for the sake of using a delegate. In such cases, there is no need for multiple targets, and the code involved is often relatively short and simple. Anonymous methods is a new feature in C# 2.0 that lets you define an anonymous (that is, nameless) method called by a delegate.
For example, the following is a conventional SomeMethod method definition and delegate invocation:
class SomeClass
{
delegate void SomeDelegate();
public void InvokeMethod()
{
SomeDelegate del = new SomeDelegate(SomeMethod);
del();
}
void SomeMethod()
{
MessageBox.Show("Hello");
}
}
You can define and implement this with an anonymous method:
class SomeClass
{
delegate void SomeDelegate();
public void InvokeMethod()
{
SomeDelegate del = delegate()
{
MessageBox.Show("Hello");
};
del();
}
}
The anonymous method is defined in-line and not as a member method of any class. Additionally, there is no way to apply method attributes to an anonymous method, nor can the anonymous method define generic types or add generic constraints.
You should note two interesting things about anonymous methods: the overloaded use of the delegate reserved keyword and the delegate assignment. You will see later on how the compiler implements an anonymous method, but it is quite clear from looking at the code that the compiler has to infer the type of the delegate used, instantiate a new delegate object of the inferred type, wrap the new delegate around the anonymous method, and assign it to the delegate used in the definition of the anonymous method (del in the previous example).
Anonymous methods can be used anywhere that a delegate type is expected. You can pass an anonymous method into any method that accepts the appropriate delegate type as a parameter:
class SomeClass
{
delegate void SomeDelegate();
public void SomeMethod()
{
InvokeDelegate(delegate(){MessageBox.Show("Hello");});
}
void InvokeDelegate(SomeDelegate del)
{
del();
}
}
If you need to pass an anonymous method to a method that accepts an abstract Delegate parameter, such as the following
void InvokeDelegate(Delegate del);
first cast the anonymous method to the specific delegate type.
A concrete and useful example for passing an anonymous method as a parameter is launching a new thread without explicitly defining a ThreadStart delegate or a thread method:
public class MyClass
{
public void LauchThread()
{
Thread workerThread = new Thread(delegate()
{
MessageBox.Show("Hello");
});
workerThread.Start();
}
}
In the previous example, the anonymous method serves as the thread method, causing the message box to be displayed from the new thread.


Passing Parameters to Anonymous Methods
When defining an anonymous method with parameters, you define the parameter types and names after the delegate keyword just as if it were a conventional method. The method signature must match the definition of the delegate to which it is assigned. When invoking the delegate, you pass the parameter's values, just as with a normal delegate invocation:
class SomeClass
{
delegate void SomeDelegate(string str);
public void InvokeMethod()
{
SomeDelegate del = delegate(string str)
{
MessageBox.Show(str);
};
del("Hello");
}
}
If the anonymous method has no parameters, you can use a pair of empty parens after the delegate keyword:
class SomeClass
{
delegate void SomeDelegate();
public void InvokeMethod()
{
SomeDelegate del = delegate()
{
MessageBox.Show("Hello");
};
del();
}
}
However, if you omit the empty parens after the delegate keyword altogether, you are defining a special kind of anonymous method, which could be assigned to any delegate with any signature:
class SomeClass
{
delegate void SomeDelegate(string str);
public void InvokeMethod()
{
SomeDelegate del = delegate
{
MessageBox.Show("Hello");
};
del("Parameter is ignored");
}
}
Obviously, you can only use this syntax if the anonymous method does not rely on any of the parameters, and you would want to use the method code regardless of the delegate signature. Note that you must still provide arguments when invoking the delegate because the compiler generates nameless parameters for the anonymous method, inferred from the delegate signature, as if you wrote the following (in C# pseudocode):
SomeDelegate del = delegate(string)
{
MessageBox.Show("Hello");
};
Additionally, anonymous methods without a parameter list cannot be used with delegates that specify out parameters.
An anonymous method can use any class member variable, and it can also use any local variable defined at the scope of its containing method as if it were its own local variable. This is demonstrated in Figure 7. Once you know how to pass parameters to an anonymous method, you can also easily define anonymous event handling, as shown in Figure 8.
Because the += operator merely concatenates the internal invocation list of one delegate to another, you can use the += to add an anonymous method. Note that with anonymous event handling, you cannot remove the event handling method using the -= operator unless the anonymous method was added as a handler by first storing it to a delegate and then registering that delegate with the event. In that case, the -= operator can be used with the same delegate to unregister the anonymous method as a handler.


Anonymous Method Implementation
The code the compiler generates for anonymous methods largely depends on which type of parameters or variables the anonymous methods uses. For example, does the anonymous method use the local variables of its containing method (called outer variables), or does it use class member variables and method arguments? In each case, the compiler will generate different MSIL. If the anonymous method does not use outer variables (that is, it only uses its own arguments or the class members) then the compiler adds a private method to the class, giving the method a unique name. The name of that method will have the following format:
__AnonymousMethod$()
As with other compiler-generated members, this is subject to change and most likely will before the final release. The method signature will be that of the delegate to which it is assigned.
The compiler simply converts the anonymous method definition and assignment into a normal instantiation of the inferred delegate type, wrapping the machine-generated private method:
SomeDelegate del = new SomeDelegate(__AnonymousMethod$00000000);
Interestingly enough, the machine-generated private method does not show up in IntelliSense®, nor can you call it explicitly because the dollar sign in its name is an invalid token for a C# method (but a valid MSIL token).
The more challenging scenario is when the anonymous method uses outer variables. In that case, the compiler adds a private nested class with a unique name in the format of:
__LocalsDisplayClass$
The nested class has a back reference to the containing class called , which is a valid MSIL member variable name. The nested class contains public member variables corresponding to every outer variable that the anonymous method uses. The compiler adds to the nested class definition a public method with a unique name, in the format of:
__AnonymousMethod$()
The method signature will be that of the delegate to which it is assigned. The compiler replaces the anonymous method definition with code that creates an instance of the nested class and makes the necessary assignments from the outer variables to that instance's member variables. Finally, the compiler creates a new delegate object, wrapping the public method of the nested class instance, and calls that delegate to invoke the method. Figure 9 shows in C# pseudocode the compiler-generated code for the anonymous method definition in Figure 7.


Generic Anonymous Methods
An anonymous method can use generic parameter types, just like any other method. It can use generic types defined at the scope of the class, for example:
class SomeClass
{
delegate void SomeDelegate(T t);
public void InvokeMethod(T t)
{
SomeDelegate del = delegate(T item){...}
del(t);
}
}
Because delegates can define generic parameters, an anonymous method can use generic types defined at the delegate level. You can specify the type to use in the method signature, in which case it has to match the specific type of the delegate to which it is assigned:
class SomeClass
{
delegate void SomeDelegate(T t);
public void InvokeMethod()
{
SomeDelegate del = delegate(int number)
{
MessageBox.Show(number.ToString());
};
del(3);
}
}


Anonymous Method Example
Although at first glance the use of anonymous methods may seem like an alien programming technique, I have found it quite useful because it replaces the need for creating a simple method in cases where only a delegate will suffice. Figure 10 shows a real-life example of the usefulness of anonymous methods—the SafeLabel Windows Forms control.
Windows Forms relies on the underlying Win32® messages. Therefore, it inherits the classic Windows programming requirement that only the thread that created the window can process its messages. Calls on the wrong thread will always trigger an exception under Windows Forms in the .NET Framework 2.0. As a result, when calling a form or a control on a different thread, you must marshal that call to the correct owning thread. Windows Forms has built-in support for solving this predicament by having the Control base class implement the interface ISynchronizeInvoke, defined like the following:
public interface ISynchronizeInvoke
{
bool InvokeRequired {get;}
IAsyncResult BeginInvoke(Delegate method,object[] args);
object EndInvoke(IAsyncResult result);
object Invoke(Delegate method,object[] args);
}
The Invoke method accepts a delegate targeting a method on the owning thread, and it will marshal the call to that thread from the calling thread. Because you may not always know whether you are actually executing on the correct thread, the InvokeRequired property lets you query to see if calling Invoke is required. The problem is that using ISynchronizeInvoke complicates the programming model significantly, and as a result it is often better to encapsulate the interaction with the ISynchronizeInvoke interface in controls and forms that will automatically use ISynchronizeInvoke as required.
For example, instead of a Label control that exposes a Text property, you can define the SafeLabel control which derives from Label, as shown in Figure 10. SafeLabel overrides its base class Text property. In its get and set, it checks whether Invoke is required. If so, it needs to use a delegate to access the property. That implementation simply calls the base class implementation of the property, but on the correct thread. Because SafeLabel only defines these methods so that they can be called through a delegate, they are good candidates for anonymous methods. SafeLabel passes the delegate, wrapping the anonymous methods to the Invoke method as its safe implementation of the Text property.


Delegate Inference
The C# compiler's ability to infer from an anonymous method assignment which delegate type to instantiate is an important capability. In fact, it enables yet another C# 2.0 feature called delegate inference. Delegate inference allows you to make a direct assignment of a method name to a delegate variable, without wrapping it first with a delegate object. For example, take a look at the following C# 1.1 code:
class SomeClass
{
delegate void SomeDelegate();
public void InvokeMethod()
{
SomeDelegate del = new SomeDelegate(SomeMethod);
del();
}
void SomeMethod()
{...}
}
Instead of the previous snippet, you can now write:
class SomeClass
{
delegate void SomeDelegate();
public void InvokeMethod()
{
SomeDelegate del = SomeMethod;
del();
}
void SomeMethod()
{...}
}
When you assign a method name to a delegate, the compiler first infers the delegate's type. Then the compiler verifies that there is a method by that name and that its signature matches that of the inferred delegate type. Finally, the compiler creates a new object of the inferred delegate type, wrapping the method and assigning it to the delegate. The compiler can only infer the delegate type if that type is a specific delegate type—that is, anything other than the abstract type Delegate. Delegate inference is a very useful feature indeed, resulting in concise, elegant code.
I believe that as a matter of routine in C# 2.0, you will use delegate inference rather than the old method of delegate instantiation. For example, here is how you can launch a new thread without explicitly creating a ThreadStart delegate:
public class MyClass
{
void ThreadMethod()
{...}
public void LauchThread()
{
Thread workerThread = new Thread(ThreadMethod);
workerThread.Start();
}
}
You can use a double stroke of delegate inference when launching an asynchronous call and providing a completion callback method, as shown in Figure 11. There you first assign the method name to invoke asynchronously into a matching delegate. Then call BeginInvoke, providing the completion callback method name instead of a delegate of type AsyncCallback.


Property and Index Visibility
C# 2.0 allows you to specify different visibility for the get and set accessors of a property or an indexer. For example, it is quite common to want to expose the get as public, but the set as protected. To do so, add the protected visibility qualifier to the set keyword. Similarly, you can define the set method of an indexer as protected, (see Figure 12).
There are a few stipulations when using property visibility. First, the visibility qualifier you apply on the set or the get can only be a stringent subset of the visibility of the property itself. In other words, if the property is public, then you can specify internal, protected, protected internal, or private. If the property visibility is protected, you cannot make the get or the set public. In addition, you can only specify visibility for the get or the set, but not both.


Static Classes
It is quite common to have classes with only static methods or members (static classes). In such cases there is no point in instantiating objects of these classes. For example, the Monitor class or class factories such as the Activator class in the .NET Framework 1.1 are static classes. Under C# 1.1, if you want to prevent developers from instantiating objects of your class you can provide only a private default constructor. Without any public constructors, no one can instantiate objects of your class:
public class MyClassFactory
{
private MyClassFactory()
{}
static public object CreateObject()
{...}
}
However, it is up to you to enforce the fact that only static members are defined on the class because the C# compiler will still allow you to add instance members, although they could never be used. C# 2.0 adds support for static classes by allowing you to qualify your class as static:
public static class MyClassFactory
{
static public T CreateObject()
{...}
}
The C# 2.0 compiler will not allow you to add a non-static member to a static class, and will not allow you to create instances of the static class as if it were an abstract class. In addition, you cannot derive from a static class. It's as if the compiler adds both abstract and sealed to the static class definition. Note that you can define static classes but not static structures, and you can add a static constructor.


Global Namespace Qualifier
It is possible to have a nested namespace with a name that matches some other global namespace. In such cases, the C# 1.1 compiler will have trouble resolving the namespace reference. Consider the following example:
namespace MyApp
{
namespace System
{
class MyClass
{
public void MyMethod()
{
System.Diagnostics.Trace.WriteLine("It Works!");
}
}
}
}
In C# 1.1, the call to the Trace class would produce a compilation error (without the global namespace qualifier ::). The reason the error would occur is that when the compiler tries to resolve the reference to the System namespace, it uses the immediate containing scope, which contains the System namespace but not the Diagnostics namespace. C# 2.0 allows you to use the global namespace qualifier :: to indicate to the compiler that it should start its search at the global scope. You can apply the :: qualifier to both namespaces and types, as shown in Figure 13.


Inline Warning
C# 1.1 allows you to disable specific compiler warnings using project settings or by issuing command-line arguments to the compiler. The problem here is that this is a global suppression, and as such suppresses warnings that you still want. C# 2.0 allows you to explicitly suppress and restore compiler warnings using the #pragma warning directive:
// Disable 'field never used' warning
#pragma warning disable 169
public class MyClass
{
int m_Number;
}
#pragma warning restore 169
Disabling warnings should be generally discouraged in production code. It is intended only for analysis when trying to isolate a problem, or when you lay out the code and would like to get the initial code structure in place without having to polish it up first. In all other cases, avoid suppressing compiler warnings. Note that you cannot programmatically override the project settings, meaning you cannot use the pragma warning directive to restore a warning that is suppressed globally.


Conclusion
The new features in C# 2.0 presented in this article are dedicated solutions, designed to address specific problems while simplifying the overall programming model. If you care about productivity and quality, then you want to have the compiler generate as much of the implementation as possible, reduce repetitive programming tasks, and make the resulting code concise and readable. The new features give you just that, and I believe they are an indication that C# is coming of age, establishing itself as a great tool for the developer who is an expert in .NET.

No comments: