Introduction
As we all know, the final version of Visual Studio 2005 will be released shortly; it's time to know the new features of C#. Now C# has upgraded to v2.0 in Visual Studio 2005. It introduces several language extensions. I am going to cover a few most important language extensions below.
- Generics
- Iterators
- Partial Class
- Nullable Types
- Anonymous Methods
- Global Alias Operator (:: Operator )
- Static Class
- Fixed Size Buffers
- Friend Assemblies
- #pragma
This article intends to the C# 1. x developers and covers what-why-how the new enhancement. The philosophy behind the document is to have a quick update on C# 2.0 and leverage this in your day-to-day development work.
1. What is Generics in C#?
Generics is one of the great features of C# 2.0. It has lots of benefits, and I am going to cover all major points of generics.
Generics is a mechanism that encapsulates operations that are not specific to any particular data type; that is, it allows classes, structs, interfaces, or methods to be parameterized by the types of data they store and manipulate. Generics provide benefits like reusability, type safety, and efficiency in a way that their non-generic counterparts cannot. Generics are most commonly used with collections and the methods that operate on them. The .NET Framework 2.0 provides a new namespace System. Collections. Generics contains several new generics-based collection classes. Apart from this, you can also create custom generic types and methods to provide your own generalized solutions that are type-safe and efficient.
If we look at the collection classes of .NET Framework 1. x, there are certain limitations, like it needs to perform boxing/unboxing or up-casting/down-casting while storing and retrieving values. For example, if we store a value type in ArrayList, it performs boxing/unboxing, or if store a string in ArrayList, it performs up-casting and down-casting because it takes System. Object as a parameter, the other limitation is lack of compile-time type checking; since an ArrayList casts everything to Object to overcome the above problems in .NET framework 1. X, we can write our own type-specific collection class ( custom collection class), but again, such classes are not generic ( reusable) for more than one data type, and you have to rewrite different custom classes for each data type.
The ultimate solution to this problem is ArrayList needs a type parameter. That is precisely what generic provides. The namespace for generics is System.Collections.Generic, the following code sample demonstrates how to work with generic List<T> .
List<int> mylist = new List<int>(); // Avoid boxing/unboxing and up-casting/down-casting
mylist.Add(99); // Compile-time error if you add any other type
// mylist.Add("Wrong type passed"); // Uncommenting this line will result in a compile-time error
// Similarly, you can define your custom generic class, for example:
public class MyList<T>
{
T[] items;
int counter;
public void Add(T item)
{
// code here
}
public void Remove(T item)
{
// code here
}
}
In the above example, generics provide a facility for creating types that have type parameters ( MyList class with type parameter T). While instantiating MyList<T>, specify the type for which they are created and store data of that type without conversion to and from System. Object. Actually, the type parameter <T> acts as a placeholder until an actual type is specified for use as below.
MyList<int> objList = new MyList<int>();
objList.Add(3); // This eliminates boxing/unboxing or casting operations
// The following line will result in a compile-time error
// objList.Add("This is a test message");
The above example demonstrates only one type. Generic type declarations may have any number of type parameters; let's say for the hashtable, we need a key and value for this; you need to specify the number of parameters while defining the class as below.
public class MyHash<TKey, TValue>
{
// Rest of the code here
}
Now I am talking about how generic type instantiate and generics in the base class library 2.0 and constraints.
Generic Type Instantiation in C#
Similar to non-generic type, the IL code maintains special instructions for generic type. The first time an application creates an instance of a constructed generic type, such as myList<int>, the just-in-time (JIT) compiler of the .NET Common Language Runtime converts the generic IL and metadata to native code, substituting actual types for type parameters in the process. Subsequent references to that constructed generic type then use the same native code. The process of creating a specific constructed type from a generic type is known as a generic type instantiation, and one important thing is The .NET Common Language Runtime creates a specialized copy of the native code for each generic type instantiation with a value type but shares a single copy of the native code for all reference types because, at the native code level, references are just pointers with the same representation.
Generics in the Base Class Library
Version 2.0 of the .NET Framework base class library provides a new namespace, System.Collections.Generic, which includes several ready-to-use generic collection classes and associated interfaces. These classes and interfaces are more efficient and type-safe than the non-generic collection classes provided in earlier releases of the .NET Framework. Before designing and implementing your own custom collection classes, consider whether you can use or derive a class from one of the classes listed below.
- Collection<T> / ICollection<T>: Provides the base class for a generic collection, and the correspondence nongeneric type is CollectionBase/ ICollection
- Comparer<T>/ IComparer<T>/ IComparable<T>: Compares two objects of the same generic type for equivalence and for sorting, and the correspondence non-generics type is Comparer/IComparer/IComparable
- Dictionary<K, V>/IDictionary<K, V>: Represents a collection of key/value pairs that are organized based on the key, and the correspondence nongenerics type is Hashtable/IDictionary
- Dictionary<K, V>.KeyCollection: Represents the collection of keys in a Dictionary<K, V>.
- Dictionary<K, V>.ValueCollection: Represents the collection of values in a Dictionary<K, V>.
- IEnumerable<T>/IEnumerator<T>: Represents a collection that can be iterated using foreach, and the correspondence non-generics type is IEnumerable/IEnumerator
- KeyedCollection<T, U>: Represents a keyed collection. and the correspondence non-generics type is KeyedCollection
- LinkedList<T>: Represents a doubly linked list
- LinkedListNode<T> : Represents a node in a LinkedList<T>
- List<T> /IList<T>: Implements the IList<T> interface using an array whose size is dynamically increased as required and the correspondence non-generics type is ArrayList /IList
- Queue<T>: Represents a first-in, first-out collection of objects, and the correspondence non-generics type is Queue
- ReadOnlyCollection<T>: Provides the base class for a generic read-only collection, and the correspondence non-generics type is ReadOnlyCollectionBase
- SortedDictionary<K, V>: Represents a collection of key/value pairs that are sorted by key based on the associated IComparer<T> implementation, and the correspondence non-generics type is SortedList
- Stack<T>: Represents a simple last-in-first-out (LIFO) collection of objects, and the correspondence non-generics type is Stack
Note. Dictionary<K, V>.KeyCollection, Dictionary<K, V>. ValueCollection, LinkedList<T>, and LinkedListNode<T> do not have correspondence non-generics type. The new generic class is slightly different from, and not fully compatible with, that of the non-generic class they replace.
One of the interesting parts of Generics is constraints.
Constraints in C#
Apart from storing data based on the type parameter, it also examines an item in the list to determine whether is it valid or needs to compare it to some other item. The compiler must have some guarantee that the operator or method it needs to call will be supported by any type of argument that might be specified by client code. This guarantee is obtained by applying one or more constraints to your generic class definition.
Example
public class MyList<T>
{
public void Add(T item)
{
T x; // Define your variable x
if (Comparer<T>.Default.Compare(item, x) < 0)
{
// Code here
}
}
}
As I mentioned, it is a compile-time error because the type argument specified for T could be any type, the only members that can be assumed to exist on the item parameter are those declared by type objects, such as Equals, GetHashCode, and ToString; a compile-time error therefore occurs in the above example. Constants tell the compiler that only objects of this type or derived from this type will be used as type arguments. Once the compiler has this guarantee, it can allow methods of that type to be called within the generic class. Constraints are applied using the contextual keyword where. The following code example demonstrates the functionality we can add to the MyList<T> class by applying a base class constraint.
public class MyList<T> where T : IComparable
{
public void Add(T item)
{
T x; // Define your variable x
if (item.CompareTo(x) < 0)
{
// Code here
}
}
}
The constraint enables the generic class to use the IComparable property since all items of type T are guaranteed to be either an IComparable type or an object that inherits/implements IComparable. Multiple constraints can be applied to the same type of parameter, and the constraints themselves can be generic types as below.
public interface IComparableList<T> : IComparable<T>, IList<T>
{
// Additional members or methods if needed
}
public class MyList<T> where T : IComparableList<T>
{
// Your code here
}
The current version of C# 2.0 supports Five types of constraints.
- Where T struct: The type argument must be a value type.
- Where T class: The type argument must be a reference type.
- where T new(): The type argument must have a public parameterless constructor. When used in conjunction with other constraints, the new() constraint must be specified last.
- where T < base class name>: The type argument must be or derive from the specified base class.
- where T <interface name>: The type argument must be or implement the specified interface. Multiple interface constraints can be specified. The constraining interface can also be generic.
By constraining the type parameter, you increase the number of allowable operations and method calls to those supported by the constraining type and all types in its inheritance hierarchy. Therefore, when designing generic classes or methods, if you will be performing any operation on the generic members beyond simple assignment or calling any methods not supported by the System. Object, you will need to apply constraints to the type parameter.
When type parameters that have no constraints, such as T in public class MyList<T>{...}, are called unbounded type parameters. The unbound parameters are below the rules.
- The != and == operators cannot be used because there is no guarantee that the concrete-type argument will support these operators.
- They can be converted to and from the System. Object or explicitly converted to any interface type.
- You can compare it to null. If an unbounded parameter is compared to null, the comparison will always return false if the type argument is a value type
When a generic type parameter is used as a constraint, it is called a naked type constraint. Naked type constraints are useful when a member function with its own type parameter needs to constrain that parameter to the type parameter of the containing type, as shown in the following example.
class List<T>
{
// ...
// This method adds items of type U to the list
void Add<U>(List<U> items) where U : T
{
// Implementation of the Add method
}
}
In the previous example, T is a naked type constraint in the context of the Add method and an unbounded type parameter in the context of the List class.
Naked-type constraints can also be used in generic class definitions. Note that the naked type constraint must also have been declared within the angle brackets along with any other type parameters
public class MyClass<T, U, V> where T : V
{
// Rest of the class code here
}
The usefulness of naked type constraints with generic classes is very limited because the compiler can assume nothing about a naked type constraint except that it derives from the System. Object. Use naked type constraints on generic classes in scenarios in which you wish to enforce an inheritance relationship between two type parameters.
I conclude generics by talking about a few cool kinds of stuff in the generics class and the differences between generics and C++ templates.
Generic classes encapsulate operations that are not specific to any particular data type. In general, the generic classes are used for working with collections like lists, hashtables, stacks queues, etc., where the items are stored, retrieved, or removed regardless of the type of data. Generic class declarations follow the same rules as normal class declarations except where noted, particularly with regard to naming, nesting, and the permitted access controls. Generic class declarations may be nested inside non-generic class declarations, for example, class MyList<T> {}. One interesting thing about static fields in a generic class is A static variable in a generic class declaration is shared amongst all instances of the same closed constructed type but is not shared amongst instances of different closed constructed types. These rules apply regardless of whether the type of the static variable involves any type of parameters or not, for example.
class C<T>
{
static int count = 0;
public C()
{
count++;
}
public static int Count
{
get { return count; }
}
}
class Application
{
static void Main()
{
// Same closed constructed type because count is int type.
C<int> x1 = new C<int>();
Console.WriteLine(C<int>.Count); // Prints 1
// Different closed constructed types
C<double> x2 = new C<double>();
Console.WriteLine(C<int>.Count); // Prints 1
// Same closed constructed type because count is int type
C<int> x3 = new C<int>();
Console.WriteLine(C<int>.Count); // Prints 2
}
}
The other cool stuff of the generic class is the default keyword. In generic classes and methods, one issue that arises is how to assign a default value to a parameterized type <T> because the value can be reference type or value type. So T = null is only valid if <T> is a reference type, and T = 0 will only work for numeric value types but not for structs. The solution is to use the default keyword, which will return null for reference types and zero for numeric value types. For structs, it will return each member of the struct initialized to zero or null depending on whether they are value or reference types. The following example from the MyList<T> class shows how to use the default keyword.
public class MyList<T> where T : IComparable
{
public void Add(T item)
{
T temp = default(T);
}
}
There are lot to talk about in generic class. Let me stop here with an assumption that lets the readers explore the other features of generic class, method, and interface.
Difference between C# Generics and C++ Templates
C# Generics and C++ templates are both languages that provide support for parameterized types. C# generics are a simpler approach to parameterized types without the complexity of C++ templates. The following are the differences between C# generics and C++ templates.
- C# generics do not provide the same amount of flexibility as C++ templates. For example, it is not possible to call arithmetic operators in a C# generic class, although it is possible to call user-defined operators.
- C# does not allow non-type template parameters, such as template C<int i> {}.
- C# does not support explicit specialization, that is, a custom implementation of a template for a specific type.
- C# does not support partial specialization: a custom implementation for a subset of the type arguments.
- C# does not allow the type parameter to be used as the base class for the generic type.
- C# does not allow type parameters to have default types
- In C#, a generic type parameter cannot itself be generic, although constructed types can be used as generics. C++ does allow template parameters.
- C++ allows code that might not be valid for all type parameters in the template, which is then checked for the specific type used as the type parameter. C# requires code in a class to be written in such a way that it will work with any type that satisfies the constraints. For example, in C++, it is possible to write a function that uses the arithmetic operators + and - on objects of the type parameter, which will produce an error at the time of instantiation of the template with a type that does not support these operators. C# disallows this; the only language constructs allowed are those that can be deduced from the constraints.
2. Iterators in C#
As we are all familiar with C# foreach, it allows us to iterate over the elements of a collection of enumerables, and the GetEnumerator method returns the enumerator from the collection. If we look enumerator has certain constraints like performance, implementations, etc, Iterators make this task simpler and efficient.
An iterator is a block that specifies executable code to return a sequence of values in chronological order. That is, the Iterator code specifies how return values are generated when the foreach loop accesses each element of the collection. I mean, Iterators simplify the process of implementing IEnumerable or IEnumerator methods. Iterators keep track of current elements in the collection, and the job of the developers is to concentrate on getting the values returned by the iterators. In Iterators, the values are returns based on something called Yield. The prime job of yields is to provide a value to the enumerator object, and it should be used inside an iterator block which might be used as a body of a method or an operator. An Iterator has certain restrictions unsafe code is not allowed inside the method body or operators, the parameters of the methods should not be ref or out, and yields should not appear inside the final block. Similarly, an iterator is distinguished from a normal statement block by the presence of one or more yield statements, such as a yield return statement produces the next value of the iteration, and a yield break statement indicates that the iteration is complete.
An iterator may be used as the body of a function member as long as the return type of the function member is one of the enumerator interfaces or one of the enumerable interfaces.
- The enumerator interfaces are System.Collections.IEnumerator and types constructed from System.Collections.Generic.IEnumerator<T>.
- The enumerable interfaces are System.Collections.IEnumerable and types constructed from System.Collections.Generic.IEnumerable<T>.
It is important to understand that an iterator is not a kind of member but is a means of implementing a function member. A member implemented via an iterator may be overridden or overloaded by other members, which may or may not be implemented with iterators.
Look at the below example. This is a simple collection class that stores the days of the week as strings. For each iteration of a foreach loop, a different day of the week string is returned.
using System;
using System.Collections;
using System.Collections.Generic;
public class List
{
public static IEnumerable<int> Power(int number, int exponent)
{
int counter = 0;
int result = 1;
while (counter++ < exponent)
{
result = result * number;
yield return result;
}
}
static void Main()
{
// Display powers of 2 up to the exponent 8:
foreach (int i in Power(2, 8))
Console.Write("{0} ", i);
}
}
Output
In the above example, the yield keywords are used to return the value(s). When the yield return statement is reached, the current location is stored. Execution is restarted from this location the next time the iterator is called. The iterator block must be called multiple times (seven times in this example) before its own for loop finishes returning all its values. This is what the calling foreach does. Iterators are especially useful with collection classes, providing an easy way of iterating non-trivial data structures such as binary trees. Below are some of the important points to be kept in mind.
- An iterator is a section of code that returns an ordered sequence of values.
- An iterator can be used as the body of a method, an operator, or a get accessor.
- The interactor code uses the yield return statement to return each element in turn.
- Using iterators, it is no longer necessary to implement the interfaces System.Collections.IEnumerable and System.Collections.IEnumerator when creating a collection class that supports foreach. The compiler does this work for you.
- The return type of an iterator must be System.Collections.IEnumerable, System.Collections.IEnumerator or one of the generic iterator interfaces.
The other feature of iterators is you can give names to the iterators, such as MyIterator Let's rewrite the previous example with named iterators.
using System;
using System.Collections.Generic;
public class List
{
public IEnumerable<int> MyIterator(int number, int exponent)
{
int counter = 0;
int result = 1;
while (counter++ < exponent)
{
result = result * number;
yield return result;
}
}
static void Main()
{
List myList = new List();
// Display powers of 2 up to the exponent 8:
foreach (int i in myList.MyIterator(2, 8))
Console.Write("{0} ", i);
}
}
Likewise, you can use iterators with Generics, for example, creating iterators block for Generic list as below.
using System;
using System.Collections.Generic;
class MyGenericClass<T>
{
T[] value;
public void AddElement(T[] t)
{
value = t;
}
public IEnumerator<T> GetEnumerator()
{
foreach (T t in value)
yield return t;
}
}
class MainClass
{
static void Main()
{
MyGenericClass<int> myGenericObject = new MyGenericClass<int>();
myGenericObject.AddElement(new int[] { 1, 3, 5, 7, 9 });
Console.WriteLine("The elements of \"myGenericObject\" are:");
foreach (int i in myGenericObject)
{
Console.WriteLine(i);
}
}
}
value = t; } public IEnumerator<T> GetEnumerator() { foreach (T t in value) yield return t; } } class MainClass { static void Main() { MyGenericClass<int> myGenericObject = new MyGenericClass<int>(); myGenericObject.AddElement(new int[] { 1, 3, 5, 7, 9 }); Console.WriteLine("The elements of \"myGenericObject\" are:"); foreach (int i in myGenericObject) { Console.WriteLine(i); } } }
Output
3. Partial class in C#
Partial class is one of the language enhancements in Visual Studio 2005. Partial class means the class definition can be split into multiple physical files (.cs or .vb files). Each source file contains a section of class definition, and at compile time compiler groups together all partial classes and treats them as a single entity. The benefits of partial class are.
- It spreads the class into multiple files and allows the developers to work on different parts of the class instead of sharing the same physical file. We often face this problem while working on large projects.
- It separates the business logic and system-generated code ( code generated when creating Windows form or web service wrapper code ). This makes debugging easier because the designer-generated code is separated from business logic and prevents developers from messing with the code.
To split the class, use the partial keyword, for example.
// PartialDemo1.cs
partial public class MyClass
{
public TestMethod1() { }
}
// PartialDemo2.cs
partial public class MyClass
{
public TestMethod2() { }
}
Note. Partial class does not make any difference to the compiler; during compile time, it simply groups all the partial classes and treats them as a single entity (class).
There are a few points one should register in his/her mind while working with a partial class, as below.
- Partial type definition must be in the same assembly/module (dll or exe), and it can not span multiple modules
- All partial type definitions must be meant to be parts of the same type and must be specified with partial keywords.
partial public class MyClass { }
public class MyClass { } // generates a compile-time error
- Generics type can be partial to the class name, and generic type parameters must match. For example
partial public class MyList<T>
{
T[] items;
int counter;
public void Add(T item)
{
// code here
}
}
partial public class MyList<T>
{
public void Remove(T item)
{
// code here
}
}
Note. You can also specify the constraints (where clauses) along with partial type definition.
- Any accessibility modifier is allowed with partial type definitions like public/private/protected/internal.
- .Nested partial type allowed in the partial type definition, For example
// Myclass1.cs
partial class Myclass
{
partial class MyNested { }
}
// Myclass2.cs
partial class Myclass
{
partial class MyNested { }
}
- Attributes of partial classes are merged at compile time. For example
// Myclass1.cs
[XYZ]
partial class Myclass { }
// Myclass2.cs
[ABC]
partial class Myclass { }
4. Nullable types in C#
Suppose we look at the existing Framework 1. X One of the main differences between reference type and value type is reference type supports null value. That means if you do not want the reference type ( such as string, class) to refer to any value, simply assign null (Nothing in VB.NET) to it as the value type always contains a value. One interesting thing about value type is that even if you explicitly pass null to it, value type just gets assigned to its default value; let's say if you assign null to an integer, it is just assigned to Zero. Framework 2.0 allows to assignment of null to variable types; it's called Nullable types.
The next question is, what are the benefits of nullable types? To answer this question, let me explain one example let's say you are interacting with a database column called Employee. Salary, In some scenarios, it stores NULL ( salary of retired employee). If you capture this value to an integer, it stores zero, which means it misleads that still the employee is working without salary, but for this, we have a System.Data.SqlTypes namespace, But those types do a lot of extra SQL-type stuff. Wouldn't it be nice to have this for all value types? There are a few other ways to assign null to primitive data types like boxed value type. This is not strongly typed at compile-time and involves doing a heap allocation for every type or a class wrapper for the value type. This is strongly typed but still involves a heap allocation, and you have to write the wrapper, which is not recommended and involves performance issues. One best solutions to this problem is Nullable Type.
Nullable types address the scenario where you want to be able to have a primitive type with a null value. Nullable types are constructed using the? type modifier. For example, int? is the nullable form of the predefined type int. A nullable type's underlying type must be a value type. Now how it is possible? yes, the answer is because of Generics, for example.
// Nullable Type Example
int? x;
if (x != null)
{
Console.WriteLine(x.Value);
}
else
{
Console.WriteLine("Undefined");
}
If we look at the design of nullable type in VS2005, it's nothing but a nullable type is a structure that combines a value of the underlying type with a boolean null indicator. An instance of a nullable type has two public read-only properties: HasValue, of type bool, and Value, of the nullable type's underlying type. HasValue is true for a non-null instance and false for a null instance. When HasValue is true, the Value property returns the contained value. When HasValue is false, an attempt to access the Value property throws an exception as below.
struct Nullable<T>
{
public bool HasValue;
public T Value;
}
You can use this struct directly, but we have also added some shortcut syntax to make the resulting code much cleaner. The first is the introduction of a new syntax for declaring a nullable type rather than typing.
Nullable<int> x = new Nullable<int>(125);
You can write.
int? x = 125; // more simplified way of working
Likewise, if I wanted to add two nullable ints together and preserve null, then here is the sample code.
int? x = 125;
int? y = 33;
int? z = x + y;
Let's have a quick recap of the properties of of Nullable type.
- Nullable types are used to create variables that can contain an undefined state.
- The syntax T? is shorthand for System.Nullable<T>, where T is a data type. The two forms are interchangeable.
- The HasValue property returns true if the variable contains a value or false if it is null.
- The Value property returns a value if one is assigned; otherwise, a System.InvalidOperationException is thrown.
- The default value for a nullable type variable sets HasValue to false. The Value is undefined.
5. Anonymous Method in C#
The anonymous method is one of the new features of C# 2.0. As the name implies, it is a nameless method called by a delegate, which means it is a mechanism to pass a block of code as a parameter. The anonymous method reduces the coding overhead in instantiating delegates by eliminating the need to create a separate method, increasing usability and code maintainability, for example, in .NET Framework 1. X to invoke a delegate, we attach a method to it as below.
partial class MyClass
{
delegate void MyDelegate();
public void TestMessage()
{
MessageBox.Show("Hello User group");
}
public void CallDelegate()
{
MyDelegate myDel = new MyDelegate(TestMessage);
MyDel();
}
}
Now let's rewrite the above code to demonstrate the anonymous method.
partial class MyClass
{
delegate void MyDelegate();
public void CallDelegate()
{
MyDelegate myDel = delegate()
{
MessageBox.Show("Hello User group");
};
MyDel();
}
}
Anonymous method is defined in-line and not as a member of the class while invoking the delegate. Anonymous method can be used anywhere that a delegate is expected (you can pass an anonymous method into any method that accepts the appropriate delegate type as a parameter). One very good example is while creating a thread containing the code that the thread executes without creating an additional delegate method, as below.
Thread myThread = new Thread(delegate()
{
//code here
}
myThread.Start();
Syntax
delegate (parameter list)
{
block of code
}.
Note. The scope of the parameter is the anonymous method block.
The following are the few important points you should keep in mind while working with the anonymous method.
- Unsafe code can not be accessed with an anonymous block.
- It is an error to have statements like goo, break, or continue inside the anonymous block whose target is outside the block.
- It is also an error to have the above statements outside the anonymous block whose target is inside the block.
- The anonymous method can not access the ref or out parameters of an outer scope.
- 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.
- The parameter list of a delegate is compatible with an anonymous method if an anonymous method has no parameter list and the delegate has no out parameters, or the anonymous method includes a parameter list that exactly matches the delegate's parameters in number, types, and modifiers.
- The return type of a delegate is compatible with an anonymous method if The delegate's return type is void and the anonymous method has no return statements or only return statements with no expression or the delegate's return type is not void, and the expressions associated with all return statements in the anonymous method can be implicitly converted to the return type of the delegate.
6. Global Alias Operator in C#
This is called namespace alias qualifier (:: operator), sometime When we add a type or assembly to another assembly, the names of those types or namespaces may conflict with names because they are already in use in the current assembly, for example.
class MyClass
{
public int Console = 7;
static void Main()
{
Console.WriteLine("Test Message"); // compile time error
}
}
If we observe Console, writeLine causes a compile-time error because the System namespace is hidden by MyClass. Sometimes we need to compromise with the variable name in order to overcome this problem, but in C# 2.0, it can be resolved using a Global Alias Operator like global::Console.WriteLine("Test Message"). The key point is when the left identifier is global, the search for the right identifier starts at the global namespace, and you can specify your class as a global space like class MyClass: global::MyParent.The next question is, what about defining or creating your own namespace called System? The answer is it is strongly recommended that we should not create a namespace called system. The global namespace qualifier is your guarantee that you are calling the root namespace. the:: operator is useful in a large project. It's very rare, but sometimes we face that namespace duplication may occur in one to another form.
7. Static class in C#
In C# 1. X if all the members and methods of a class are static and can not be instantiated i.e. having a pubic constructor does not make any sense, to prevent instantiation of such a class, we have to make the constructor private, and one important thing is class has to mark as sealed because static members are not meant for inheritance. In C# 2.0, the static class makes the above tasks more safe and convenient way, The Static class marks the class as sealed with no constructor, and it's a compile-time error if you instantiate the static class. The main problem of the earlier approach ( C# 1. X) is the private constructor takes up space in the IL. Actually, it's not a big issue but has little effect or accidentally, a developer can derive a class from it if you forget to mark it as sealed, and finally, there is no protection against the instance method. The main advantages of a static class are.
- Static classes only contain static members.
- Static classes cannot be instantiated.
- Static classes are sealed.
- Static classes cannot contain a constructor.
The following example demonstrates static class.
public static class MyStaticClass
{
public static void DispMessage(string strMessage)
{
Console.WriteLine(strMessage);
}
}
partial class Class1
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
MyStaticClass.DispMessage("This is a static class");
}
}
The answer is very simple if the class's methods do not need to be attached to a specific instance of the class, instead of creating unnecessary instances of this class, declare it as a static class for example, if you have a class called Company contains only Two methods called GetCompanyName() and GetCompanyAddress() these methods do not need to be attach to a specific instance of any class so you can make this a static class and call where ever you need company information instead of making unnecessary instance of the class.
8. Fixed Size Buffer
Fixed-size buffer provides flexibility to create fixed-size data structure useful when you are working with unmanaged world such as DLLs or COM components. For example, it's impossible to declare a C++ style fixed-size structure because C# struct containing an array doesn't contain the array elements but instead contains a reference to the elements in C# 2.0, providing flexibility to embed an array of fixed size in a struct when used in an unsafe code block as below.
public struct Employee
{
public fixed char EmpName[50];
public int EmpID;
}
The first element is a 50-byte array, and EmpID is a 4-byte int. The main advantage of the above code is EmpName array is of is fixed size and location and can be used with unsafe code.
If we want to implement the above in C# 1. X, it will be as below.
public struct Employee
{
public string EmpName; // Use string for variable-sized text data
public int EmpID;
}
The difference between the above code and the C# 2.0 fixed size buffer is that the above code struct would be 8 bytes in size, where the EmpName array is a reference to the heap-allocated array.
The other difference between an unsafe buffer array and a normal array is.
- You can only use unsafe buffers in an unsafe context.
- Unsafe buffers are always one-dimensional arrays.
- The declaration of the array should include a count, for example, char Name[15]. You cannot use char Name[] instead.
- Unsafe buffers can only be instance fields of structs in an unsafe context.
- The C# compiler and the CLR do not perform any security buffer overrun checks as with all unsafe code.
9. Friend Assemblies in C#
The friend assemblies are one of the cool stuff of C# 2.0. The philosophy behind friend assemblies is all the non-public types in an assembly can be accessed by another assembly by declaring the first assembly as a "friend" of the second For example, you have some types in your assembly A, and you have another assembly B. You really want assembly B to see those types. But you don't want them to open to the world. In order to implement in C# 2.0, we have to make assembly B a friend of assembly A. A friend assembly is declared by using the InternalsVisibleToAttribute as below.
[assembly: InternalsVisibleTo("AssemblyB, PublicKeyToken=32ab4ba45e0a69a1")]
In C# 1. X, you can implement by marking those types as public and then decorating them with StrongNameIdentityPermission.
The main difference between StrongNameIdentityPermission and friend assembly is.
- StrongNameIdentityPermission applies to individual types, while friend assembly applies to the whole assembly example; if there are hundreds of types in assembly A that you want to share with assembly B, you have to decorate all of them with StrongNameIdentityPermission. While using friend assembly, you only need to state it once.
- More importantly, with StrongNameIdentityPermission, the types you want to share have to be declared as public. In the friend assembly case, those types can (and should) be nonpublic.
- There can be only one StrongNameIdentityPermission on one type. But you can declare as many assemblies as your friends.
Finally, we must keep two things in mind while working with friend assemblies. Friend Assemblies are one way only, which means When assembly A says assembly B is its friend, it does not imply assembly A is assembly B's friend. You will have to declare the friendship explicitly in assembly B, and Friendship is not transitive. That means if Assembly C is a friend of Assembly B, and Assembly B is the friend of Assembly A, Assembly C does not automatically become a friend of Assembly A.
10. #Pragma warning in C#
C# language has many preprocessor directives like #if,#else,#endif Similarly, #pragma warning is one type of preprocessor directive. These directives are used to aid in conditional compilation. Unlike C and C++ directives, you cannot use these directives to create macros, and the preprocessor directive must be the only instruction on a line.
The #Pragma warning directive is used to enable or restore all or certain warning messages during compilation.
Syntax
#pragma warning disable warning-list (disable directive disables the specified set of warnings)
#pragma warning restore warning-list (restore directives restore the specified set of warnings)
If you disable the warning externally ( I mean by setting /nowarn option), # pragma restore will not enable the warning.
For example, disable and restore the CLSCompliant attribute (cs3021).
// pragma_warning.cs
using System;
#pragma warning disable 3021
[CLSCompliant(false)]
public class MyClass
{
int i = 1;
public static void Main()
{
// Write your code here
}
}
#pragma warning restore 3021
[CLSCompliant(false)]
public class MyClass
{
int i = 1;
public static void Main()
{
// Write your code here
}
}