
It's no secret that C # is a popular and dynamically developing language today, unlike its direct competitor, Java, which in terms of functionality is going through a period of stagnation. The main indisputable advantage of Java is real cross-platform, not dull and limited, like in C #.
C # is a simple language, thanks to its simplicity, PHP also lives. But at the same time, it is very functional, and has the status of a "hybrid" language, combining various paradigms, built-in support for both the imperative programming style and the functional one.
Like any language, Sharp has its own subtleties, features, "pitfalls" and little-known features. What I mean? Read under the cut ...
Packing and unpacking - everyone knows, but not everyone
Reference types (object, dynamic, string, class, interface, delegate) are stored in a managed heap, value types (struct, enum; bool, byte, char, int, float, double) are in the application stack (except when the value type is a class field). Converting a value type to a reference type is accompanied by an implicit
boxing operation — putting a
copy of the value type into a wrapper class, an instance of which is stored on the heap. The packaging type is generated by the CLR and implements the interfaces of the stored value type. Converting a reference type to a value type causes an
unboxing operation — retrieving a copy of the value type from the package and placing it on the stack.
using System;
class Program
{
static void Main()
{
int val = 5;
object obj = val; //
int valUnboxed = ( int )obj; //
}
}
Corresponding IL code:
')
.locals init ([0] int32 val, [1] object obj, [2] int32 valUnboxed)
IL_0000: nop
IL_0001: ldc.i4.5
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: box [mscorlib]System.Int32
IL_0009: stloc.1
IL_000a: ldloc.1
IL_000b: unbox.any [mscorlib]System.Int32
IL_0010: stloc.2
IL_0011: ret
Packing and unpacking are relatively slow operations (meaning copying), so they should be avoided if possible. The following code displays non-obvious cases leading to packaging:
using System;
class Program
{
static void Main()
{
// 1.
IComparable< int > iComp = 1;
// 2. enum System.Enum
Enum format = UriFormat.Unescaped;
// 3. dynamic
dynamic d = 1;
}
}
In msdn, it is recommended to avoid value types in cases when they have to be packed many times, for example, in non-universal collection classes (ArrayList). Packing-type value conversions can be avoided using generic collections (System.Collections.Generic namespace). It should also be remembered that dynamic at the level of IL code is the same object, only (not always) marked with attributes.
Lambda recursion - about malicious closure
Let us turn to the classical implementation of the recursive calculation of factorial using lambda expressions:
using System;
using System.Numerics;
class Program
{
static void Main()
{
Func< int , BigInteger> fact = null ;
fact = x => x > 1 ? x * fact(x - 1) : 1;
}
}
We know that lambda can invoke itself because of its ability to capture environment variables (closure). We know that captured objects can be modified outside the context of lambda expressions, and there is no built-in language ability to override this behavior. It is clear that in
most cases this approach will not be acceptable, and I would like to somehow limit the ability to change the captured variable outside the lambda context.
In the general case, the presented problem is solved by implementing
a fixed point combinator :
using System;
using System.Numerics;
class Program
{
static void Main()
{
var fact = YPointCombinator.Create< int , BigInteger>(f => (n) => n > 1 ? n * f(n - 1) : 1);
var power = YPointCombinator.Create< int , int , BigInteger>(f => (x, y) => y > 0 ? x * f(x, y - 1) : 1);
}
}
public static class YPointCombinator
{
public static Func<T1, T2> Create<T1, T2>(Func<Func<T1, T2>, Func<T1, T2>> f)
{
return f(r => Create( f )( r ));
}
public static Func<T1, T2, T3> Create<T1, T2, T3>(Func<Func<T1, T2, T3>, Func<T1, T2, T3>> f)
{
return f((r1, r2) => Create(f)(r1, r2));
}
}
Fields private and reflection, or we spat on your OOP
With the help of the reflection mechanism, it is possible to change the value of even a private field of a class.
It is clear that to use this build only in case of emergency, while respecting the principle of encapsulation.
using System;
using System.Reflection;
class Sample
{
private string _x = "No change me!" ;
public override string ToString()
{
return _x;
}
}
class Program
{
static void Main()
{
var sample = new Sample();
typeof (Sample).GetField( "_x" , BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(sample, "I change you..." );
Console .Write(sample);
Console .ReadKey();
}
}
UPD: As
braindamaged rightly remarked, it will be possible to change the private field only if the assembly belongs to a code group with the necessary permissions. You can declare such authority declaratively by marking the class (method) with something like this:
[System.Security.Permissions.ReflectionPermission(System.Security.Permissions.SecurityAction.Assert)]
With the .NET security system, not everything is simple, and in .NET 4 it has undergone
major changes .
"Duck" typing and foreach cycle
To be able to iterate over the elements of an instance of a certain class using foreach, it suffices to implement the GetEnumerator () method in it.
using System;
using System.Collections.Generic;
class Sample
{
public IEnumerator< int > GetEnumerator()
{
for ( var i = 0; i < 10; ++i)
yield return i;
}
}
class Program
{
static void Main()
{
foreach ( var t in new Sample())
Console .WriteLine(t);
Console .ReadKey();
}
}
This small manifestation of the so-called
"duck" typing , commonly used in dynamic languages, occurs in C #.
Anonymous types - more
Variables of an anonymous type can be saved in a collection. See for yourself:
using System;
using System.Linq;
class Program
{
static void Main()
{
var list = new [] {
new { Name = "Alex" , Age = 18 },
new { Name = "Petr" , Age = 30 } }.ToList();
Console .Write(list.Find(x => x.Name == "Petr" ));
Console .ReadKey();
}
}
Variables of anonymous type can be passed to another scope:
using System;
class Program
{
static dynamic User
{
get { return new { Name = "Alex" , Age = 18 }; }
}
static void Main()
{
Console .Write(User.Name);
Console .ReadKey();
}
}
ref can sometimes be omitted
Starting in C # 4.0, the ref keyword can be omitted when calling a method via COM Interop. In combination with the named arguments looks very impressive:
using System;
using Word = Microsoft.Office.Interop.Word;
class Program
{
static void Main()
{
var app = new Word.Application();
Word.Document doc = null ;
// C# 2.0 - 3.5
object
filename = "test.doc" ,
visible = true ,
missing = Type.Missing;
doc = app.Documents.Open(
ref filename, ref missing, ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing, ref missing, ref visible,
ref missing, ref missing, ref missing, ref missing);
// C# 4.0
doc = app.Documents.Open(FileName: "test.doc" , Visible: true );
}
}
Note: the named parameters and the ability to omit ref are language tools, therefore, the .NET Framework 4.0 and the .NET Framework 2.0, 3.0, 3.5 can be selected as the base application framework.
What's left overs
Among all the other "subtleties" of the language, I would single out the problem of deterministic destruction of objects, the complexity of handling asynchronous exceptions such as ThreadAbortException. Of interest are the powerful means of synchronizing threads and the upcoming changes in C # 5.0 related to embedding into the language support for asynchronous operations.