8 facts you may not have known about C #

Original author: Damien Guard
  • Transfer
Here are some unusual facts about the C # language that few developers know about.

1. Indexers can use params parameters


We all know how x = something ["a"] indexers usually look, as well as the code needed to implement it:

public string this[string key]
 {
   get { return internalDictionary[key]; }
 }

But did you know that to access the elements you can use params parameters x = something ["a", "b", "c", "d"]?
Just write your indexer as follows:

public IEnumerable this[params string[] keys]
 {
   get { return keys.Select(key => internalDictionary[key]).AsEnumerable(); }
 }

The great news is that you can use multiple indexers together in the same class. If someone passes an array or several arguments as a parameter, he will get IEnumerable as a result, but by calling, an indexer with one parameter, he will get a single value.

2. String literals defined in your code are stored in a single copy


Many developers think the following code:

if (x == "" || x == "y")

will create a couple of lines every time. No, it will not. C #, like many other programming languages, uses string interning, and each literal string used in your application is put into a special list (hash table) called a string pool, from where a link to it is taken at runtime.

You can use the String.IsInterned method to determine if a string is in the internment pool at the moment, but remember that the expression String.IsInterned ("what") == "what" will always be true.
String.IsInterned ("wh" + "at") == "what" will also always be true, thanks to the compiler for the optimization. String.IsInterned (new string (new char [] {'w', 'h', 'a', 't'it will only be true if the literal string “what” has already been used in your code or if it was manually added to the internment pool.

If you have classes that regularly use strings, consider using the String.Intern method to add them to the string pool. However, be careful, as in this case they are stored until the application terminates, so use String.Intern carefully. To add a string to the pool, simply call the String.Intern (someClass.ToString ()) method. Another precaution is that (object) "Hi" == (object) "Hi" is always true, thanks to interning. Try to check this code in the interpretation window, and the result will be negative, since the debugger does not intern your lines.

3. Casting types to less specialized ones does not prevent you from using their real type


A great example of this is a property that returns IEnumerable, whose real type is List, for example:

private readonly List internalStrings = new List();
public IEnumerable AllStrings { get { return internalStrings; }

You think no one can change the list of strings. Alas, this is too easy to do:

((List)x.AllStrings).Add("Hello");

Even the AsEnumerable method will not help here. You can use the AsReadOnly method, which will create a wrapper over the list and will throw an exception every time you try to change it, however it provides a good pattern for similar things with your classes.

4. Variables in methods can have a scope limited by curly braces


In the Pascal programming language, you must describe all the variables that will be used at the beginning of your function. Fortunately, today variable declarations can exist next to their assignment, which prevents them from being used accidentally before you intend.

However, this does not stop you from using them after they are no longer needed. We can describe temporary variables using the keywords for / if / while / using. You will be surprised, but you can also describe temporary variables using curly braces without keywords to get the same result:

private void MultipleScopes()
 {
   { var a = 1; Console.WriteLine(a); }
   { var b = 2; Console.WriteLine(a); }
 }

This code does not compile because of the second line, because it refers to a variable that is enclosed in braces. Such a partition of the method into parts with curly braces is quite useful, but a much better solution is to divide the method into smaller ones using the refactoring method: extracting the method.

5. Enumerations may have extension methods


Extension methods provide the ability to write methods for existing classes so that people from your team can use them. Given that enumerations are similar to classes (more precisely, structures), you should not be surprised that they can be expanded, for example:

enum Duration { Day, Week, Month };
static class DurationExtensions
 {
   public static DateTime From(this Duration duration, DateTime dateTime)
    {
     switch (duration)
      {
         case Duration.Day: return dateTime.AddDays(1);
         case Duration.Week: return dateTime.AddDays(7);
         case Duration.Month: return dateTime.AddMonths(1);
         default: throw new ArgumentOutOfRangeException("duration");
        }
    }
 }

I think the enumerations are evil, but at least extension methods can get rid of some conditional checks (switch / if) in your code. Remember to check that the enumeration value is in the right range.

6. The order in which you describe static variables matters


Some people claim that the variables are arranged in alphabetical order, and that there are special tools that can change their order for you. However, there is a scenario in which reordering can break your application.

static class Program
 {
   private static int a = 5;
   private static int b = a;
   static void Main(string[] args)
    {
       Console.WriteLine(b);
    }
 }

This code will print the number 5. If you swap the description of the variables a and b, it will print 0.

7. Private class instance variable may be available in another instance of this class


You might think that the following code will not work:

class KeepSecret
 {
   private int someSecret;
   public bool Equals(KeepSecret other)
    {
      return other.someSecret == someSecret;
    }
  }

It is easy to think that private means only a given instance of a class, can have access to it, but in reality this means that only this class has access to it ... including other instances of this class. In fact, this is very useful when implementing some comparison methods.

8. C # language specification already on your computer


If you have Visual Studio installed, you can find the specification in the VC # \ Specifications folder. VS 2011 comes with the C # 5.0 language specification in Word format.

It is full of many interesting facts such as:
  • i = 1 - atomic (thread safe) for int but not for long;
  • You can use the & and | operators to nullable boolean types (bool?), Ensuring compatibility with ternary SQL logic;
  • Using [Conditional ("DEBUG")] is preferable to #if DEBUG.


And to those of you who say, “I knew all / most of this,” I say, “Where are you when I hire developers?” Seriously, it's hard enough to find a C # developer with good language skills.

From myself I will add a couple of facts:

9. You cannot use a constant named value__ in an enumeration


I think this fact is known to many developers. In order to understand why it is so enough to know what the enumeration is compiled.

The following listing:

public enum Language
 {
   C,
   Pascal,
   VisualBasic,
   Haskell
 }

the compiler sees something like this:

public struct Language : Enum
 {
   //Открытые константы, определяющие символьные имена и значения
   public const Language C = (Language)0;
   public const Language Pascal = (Language)1;
   public const Language VisualBasic = (Language)2;
   public const Language Haskell = (Language)3;
   //Открытое поле экземпляра со значением переменной Language
   //Код с прямой ссылкой на этот экземпляр невозможен
    public Int32 value__;
  }

Now it’s clear that the variable name value__ is simply reserved to store the current value of the enumeration.

10. Field initializer allows too much


We all know how the field initializer works.

class ClassA
 {
   public string name = "Tim";
   public int age = 20;
   public ClassA()
    { }
   public ClassA(string name, int age)
    {
     this.name = name;
     this.age = age;
    }
  }

The initialization code is simply placed in each of the constructors.

class ClassA
  {
    public string name;
    public int age;
    public ClassA()
    {
      this.name = "Tim";
      this.age = 20;
      base..ctor();
    }
    public ClassA(string name, int age)
    {
      this.name = "Tim";
      this.age = 20;
      base..ctor();
      this.name = name;
      this.age = age;
    }
  }

But if one constructor calls another, then initialization is placed only in the called constructor ...

class ClassA
 {
   public string name = "Tim";
   public int age = 20;
   public ClassA()
    { }
   public ClassA(string name, int age):this()
    {
     this.name = name;
     this.age = age;
    }
 }

which is equivalent

class ClassA
  {
    public string name;
    public int age;
    public ClassA()
    {
      this.name = "Tim";
      this.age = 20;
      base..ctor();
    }
    public ClassA(string name, int age)
    {
      this..ctor();
      this.name = name;
      this.age = age;
    }
  }

And now the question is, what happens if the designers call each other? Where does the compiler decide to put the initialization code?

Answer: the compiler will decide that since the first constructor calls the second, I will not put initialization in the first. He will draw similar conclusions with the second designer. As a result, the initialization code will not be placed in any constructor ... And since it does not need to be placed anywhere, then why should it be checked for errors? Thus, in the initialization code, you can write anything you want. For instance,

class ClassA
 {
   public string name = 123456789;
   public int age = string.Empty;
   public ClassA():this("Alan", 25)
    { }
   public ClassA(string name, int age):this()
    {
     this.name = name;
     this.age = age;
    }
 }

This code compiles without problems, but it crashes with a stack overflow when creating an instance of this class.

11. You can only use C # synonyms when describing the base type of an enumeration


As you know, when describing an enumeration, we can explicitly indicate the base type underlying it. The default is int.

We can write like this

public enum Language : int
 {
   C,
   Pascal,
   VisualBasic,
   Haskell
 }

however we cannot write like that

public enum Language : Int32
 {
   C,
   Pascal,
   VisualBasic,
   Haskell
 }

The compiler requires exactly C # type synonym.

12. Structures are immutable when used in collections.


As you know, structures in .NET are significant types, that is, they are passed by value (copied), which is why mutable structures are evil. However, the fact that changeable structures are evil does not mean that they cannot be changed. By default, structures are mutable, that is, their state can be changed. But if structures are used in collections, then they cease to be mutable. Consider the code:

struct Point
 {
   public int x;
   public int y;
 }
static void Main(string[] args)
 {
    var p = new Point();  p.x = 5; p.y = 9;
    var list = new List(10);
    list.Add(p);
    list[0].x = 90;//ошибка компиляции
    var array = new Point[10];
    array[0] = p;
    array[0].x = 90;//все ок
}

In the first case, we get a compilation error, because the collection indexer is just a method that returns a copy of our structure. In the second case, we won’t get an error, since indexing in arrays is not a method call, but an appeal to the right element.

Thanks for reading.
I hope this article has been helpful.

Only registered users can participate in the survey. Please come in.

And now a question for C # developers! What did you not know from the above?

  • 64.6% 1 764
  • 33.4% 2 395
  • 30.5% 3 361
  • 28% 4 332
  • 40.1% 5 475
  • 36.4% 6 431
  • 34% 7 403
  • 56.4% 8 667
  • 64.2% 9 760
  • 66.5% 10 787
  • 58.4% 11 691
  • 56.4% 12 667

Also popular now: