8 things you probably didn’t know about C#

Here’s a few unusual things about C# that few C# developers seem to know about.

1. Indexers can use params

We all know the regular indexer pattern x = something["a"] and to implement it, you write:

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

But did you know that you can use params to allow x = something["a", "b", "c", "d"] ?

You write your indexer like this:

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

The cool thing is you can have both indexers in the same class side-by-side. If somebody passes an array or multiple args, they get an IEnumerable back but call with a single arg, and they get a single value.

2. Strings defined multiple times in your code are folded into one instance

Many developers believe that:

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

creates a couple of strings every time. It doesn’t.

C#, like many languages, has string interning, and every string your app compiles with gets put into an in-memory list referenced at runtime.

You can use String.Intern to see if it’s currently in this list but bear in mind that doing String.Intern("what") == "what" will always return true as you just defined another string in your source. String.IsInterned("wh" + "at") == "what" will also return true thanks to compiler optimizations. String.IsInterned(new string(new char[] { 'w','h','a','t' }) == new string(new char[] { 'w','h','a','t' }) will only return true if you have "what" elsewhere in your program or something else at runtime has added it to the intern pool.

If you have classes that build-up or retrieve regularly used strings at runtime, consider using String.Intern to add them to the pool. Bear in mind once they’re in, they remain there until your app quits, so use String.Intern carefully. The syntax is simply String.Intern(someClass.ToString())

Another caveat is that doing (object)"Hi" == (object)"Hi" returns true in your app thanks to interning. Try it in your debug intermediate window, and it is false as the debugger does not intern your strings.

3. Exposing types as a less capable type doesn’t prevent use as their real type

A great example of this is when internal lists are exposed as IEnumerable properties, e.g.

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

You’d likely think nobody can modify internal strings. Alas, it’s all too easy:

((List<string>)x.AllStrings).Add("Hello");

Even AsEnumerable won’t help as that’s a LINQ method that does nothing :( You can use AsReadOnly, which creates a wrapper over the list that throws when you try and set anything, however, and provides a good pattern for doing similar things with your classes should you need to expose a subset of internal structures if unavoidable.

4. Variables in methods can be scoped with just braces

In Pascal you had to declare all the variables your function would use at the start of the function. Thankfully, declarations today can live next to their assignment, which prevents accidentally using the variable before you intended to.

What it doesn’t do is to prevent you from using it after you intended. Given that for/if/while/using and such allow a nested scope, it should come as no surprise that you can declare variables within braces without a keyword to achieve the same result:

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

It’s useful as now the second copy-and-pasted code block doesn’t compile, but a much better solution is to split your method into smaller ones using the extract method refactoring.

5. Enums can have extension methods

Extension methods provide a way to write methods for existing classes that other people on your team might discover and use. Given that enums are classes like any other, it shouldn’t be too surprising that you can extend them, like:

enum Duration { Day, Week, Month };

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

I think enums are a little harmful, but at least this lets you centralize some of the switch/if handling and abstract them away a bit until you can do something better. Remember to check the values are within the range too.

6. Order of static variable declaration in your source code matters

Some people insist that variables be ordered alphabetically, and there are tools around that can reorder for you. However, there is one scenario where reordering can break your app.

static class Program {
  private static int a = 5;
  private static int b = a;

  static void Main(string[] args) {
   Console.WriteLine(b);
  }
}

This prints the value 5. Reorder the a and b declarations, and it prints 0.

7. Private instance variables of a class can be accessed by other instances

You might think the following code wouldn’t work:

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

It’s easy to think of private as meaning only this instance of a class can access them. The reality is it means only this class can access it - including other instances of this class. It’s quite useful when writing some comparison methods.

8. The C# Language specification is already on your computer

Providing you have Visual Studio installed, you can find it in your Visual Studio folder in your Program Files folder (x86 if on a 64-bit machine) within the VC#\Specifications folder. VS 2010 comes with the C# 5.0 document in Word format.

It’s full of many more interesting facts such as:

  • i = 1 is atomic (thread-safe) for an int but not long
  • You can & and | nullable booleans with SQL compatibility
  • [Conditional("DEBUG")] is an alternative to #if DEBUG

And to those of you that say, “I knew all/most of these!”, I say “Where are you when I’m recruiting!” It’s hard enough to find developers with a solid understanding of the well-know parts of the language.

[)amien

41 responses

  1. Avatar for Chris Shaffer

    You say that the C# specification says that i++ is atomic/threadsafe for int; I had always understood that not to be the case, and I can't find anything in the documentation that says it is atomic. Can you point me to what you are seeing? As a separate reference, see Eric Lippert's answer to this stackoverflow question

    Chris Shaffer 29 October 2012
  2. Avatar for Ken

    I knew number three for sure, number one was fascinating but I just never seem to know when to use indexers. You should blog "Neat Things You Can Do With Indexers."

    Ken 29 October 2012
  3. Avatar for dahlbyk

    Another option for #3 is to return internalStrings.Select(x => x), as Select() implementations are supposed to guarantee that the source object itself is not returned.

    Incidentally, the spec guarantees that from x in e select x, a so-called degenerate query expression, will return the same even though trivial selects are normally optimized out.

    dahlbyk 29 October 2012
  4. Avatar for Mark

    With regard to 4, I liked the fact that in Pascal there was this declare block. In C based languages I dislike the clutter that is caused by inline declarations. Although using 'var' as much as possible improves it somewhat.

    Mark 29 October 2012
  5. Avatar for Doug

    I enjoyed this post. Thanks for sharing! And I only knew a few of them.

    Doug 29 October 2012
  6. Avatar for Oleg

    Because we have good jobs already:)

    Oleg 29 October 2012
  7. Avatar for Bruno

    Wow, the params thingy in indexers is really nice. PS.: I knew almost all of it, are you recruiting? LOL

    Bruno 29 October 2012
  8. Avatar for Craig Dean

    Really fun list (and yup I did know all of them but I'm geeky like that).

    Some 'off the top of my head' feedback.

    1. This is a great gotcha, however I tell our devs not to worry about it in most cases. If you expose a property as IEnumerable (other examples exist!) you are telling people not to assume the underlying implementation, and you may in fact change it in future. There are only a few cases where you absolutely want to prevent someone gaining access to the underlying variable, particular security, and yet in those cases Reflection quickly allows a determined dev to circumvent your carefully laid plans in pretty much every case. As such the real overhead of the creating ReadOnly collections, etc., is very rarely justified - particularly if the stated aim is to prevent consumers doing something unsupported. (IMHO) Bear in mind that LINQ frequently optimistically checks for an array or list before falling back to a brute force approach.

    2. It's shocking how few people know this one (we do 200+ interviews a year and no one gets it). Another one is how few people can tell you what protected internal is (or that it exists as an access modifier)...

    3. i++ is atomic for int on Intel, on x64 OS it's atomic for long (as is double). The Interlocked namespace is the best way to safely perform an increment is you wish it to be atomic.

    I think you mean #if DEBUG not if #DEBUG...

    Craig Dean 29 October 2012
  9. Avatar for Damien Guard

    @chris - In the C# v5 spec it's section 5.5 Atomicity of variable references.

    Damien Guard 29 October 2012
  10. Avatar for gliljas

    "i++ is atomic (thread-safe) for int but not for long"

    Ah, so next year it won't be... :)

    gliljas 29 October 2012
  11. Avatar for Harold

    section 5.5 also explicitly states: "there is no guarantee of atomic read-modify-write, such as in the case of increment or decrement."

    So no, i++ is not atomic. But i = 8 is, if i is an int.

    Harold 29 October 2012
  12. Avatar for Damien Guard

    Good catch Harold - I could have sworn I saw ++ covered in the atomic section but can't find it now. I'll put it down to reading specs at 3am. Article fixed.

    Damien Guard 29 October 2012
  13. Avatar for ER

    5 I did not know. Could make enums in code maintenance much nicer. Re: 6 - init of static variable with another static, outside of a static constructor? run away! ;)

    @glijas: very good!

    ER 30 October 2012
  14. Avatar for Alex

    I knew about all of these except the [Conditional("DEBUG")] attribute, which I certainly intend to use now. Thanks!

    Atomicity of various operations is something one learns about quickly when trying to implement lock-free structures :P

    Alex 30 October 2012
  15. Avatar for Fabio

    I'm in Brazil :)

    By the way, I didn't know about [Conditional("DEBUG")]

    Fabio 30 October 2012
  16. Avatar for Scott

    I like the string interning. It works regardless of visibility or membership.

    class Program
    {
        static string sw = "what";
        const string cw = "what";
        public string iw = "what";
    
        static void Main(string[] args)
        {
            Program prog = new Program();
            Console.WriteLine(String.IsInterned("foo"));
            Console.WriteLine(String.IsInterned("what"));
            Console.WriteLine(String.IsInterned(cw));
            Console.WriteLine(String.IsInterned(sw));
            Console.WriteLine(String.IsInterned(prog.iw));
            Console.WriteLine((sw == cw) &amp;&amp; (prog.iw == cw) &amp;&amp; (sw == prog.iw));
            Console.ReadLine();
        }
    }
    
    Scott 30 October 2012
  17. Avatar for KFee

    #5 is a good idea, I hadn't thought of that. Although with my love for extension methods, I probably would have come up with it sooner or later. The rest of them, yeah, I knew. As for "where were you when I was recruiting?", well, where were you when I was looking for a job? :-P

    KFee 30 October 2012
  18. Avatar for Andrei Rinea

    I am so disappointed that I only didn't know number 8. I was hoping for more :)

    Andrei Rinea 30 October 2012
  19. Avatar for Shannen Saez

    My guess on the "i = 1 is atomic" fact is that in x86 assembly the ADD instruction operate on 32-bit registers. Because int's are 32-bit this can be done in one instruction, where-as long is a 64-bit int and some extra instructions would be required.

    Shannen Saez 30 October 2012
  20. Avatar for Fenn

    Nice post, thanks for the tricks collection even if I knew them, except the #8, which is interesting. ;) For the #3, I would say that the best way to really prevent somebody from modifiying an internal collection is to never return that collection, but only copies (e.g. using linq .ToList), or make use of a read-only collection which is made for that (but everything depends on the problem you're trying to solve, of course). AsEnumerable doesn't do "nothing", but here it's just not its use case ;) (it's for example basically used in linq expressions to convert IQueryable to IEnumerable, so that you can switch from remote request processing to in-memory processing).

    Fenn 31 October 2012
  21. Avatar for lindholm

    If you want to hide a list with IEnumerable

    Make the code

    get { foreach(var s in list) yield return s; }

    You will not be able to cast that to a list.

    lindholm 1 November 2012
  22. Avatar for ngm

    Great post! I definitely did not know (1) and (8). After reading your post, I tried (1) on VS2012 targeting .NET 4. To my surprise, C# also let me define other indexers. I knew the CLR supported this, but I think this is a new feature to C#.

    private class ABC {
        private readonly List ss;
    
        public ABC(IEnumerable ss) {
            this.ss=ss.ToList();
        }
    
        public string this[int x] {
            get { return ss[x]; }
        }
    
        public IEnumerable this[params int[] xs] {
            get { return xs.Select(x => ss[x]); }
        }
    
        public string this[string s] {
            get { return s; }
        }
    }
    
    ngm 1 November 2012
  23. Avatar for ngm

    Ugh, looks like the comment system ate my generics brackets.

    The third indexer (which takes string) is the most surprising. I would not have thought it'd let me create both that one and the int one.

    ngm 1 November 2012
  24. Avatar for Armen Shimoon

    How about this one?

    You cannot have an enum value named __value.

    public enum TestEnum {
        __value
    }
    

    Won't compile. This is because the value of an enum instance is stored in a variable called "__value" which is generated by the compiler.

    Armen Shimoon 1 November 2012
  25. Avatar for JeyDotC

    About the 7th point, it isn't something that happens just in C#, it occurs in all Object Oriented languages.

    One uses to forget that fact, combined to the custom of respecting the objects' interfaces.

    JeyDotC 1 November 2012
  26. Avatar for Joshua C

    This was a great posting! I didn't know several, but by knowing a little IL, most items make sense. For example, wouldn't #7 be the same as saying that access is given based on the requesting object's typedef token? If private means 'per typedef token' then the CLR would have no ability, even, to discriminate based on instance rather than class. Also, the response mentioning protected intern gives a much easier question if familiar with the CLR's famandassem and famorassem attributes.

    Joshua C 3 November 2012
  27. Avatar for Javier Gonel

    Nice post!

    The first one I find it really useful.

    Side note: When you thought you were hiring, in fact you were in a "Quiz Show" or playing "trivia" with your candidates. The people you wanted to hire perhaps went to real interviews or stayed home playing trivia with their friends.

    Javier Gonel 8 November 2012
  28. Avatar for Damien Guard

    Just to clarify I would never use these as interview questions!

    I always use real-world domain-specific coding problems looking for coding ability,good design and being able to ask me the right questions.

    My point was if you know C# to this extreme you hopefully know C# very very well.

    Damien Guard 8 November 2012
  29. Avatar for Dave Rathbone

    You fail to point out that C# can run managed or un-managed code which many of its other .net counter parts can not. I just hate the fact your code time and investment is all for Microsoft platforms only!

    Dave Rathbone 10 November 2012
  30. Avatar for Damien Guard

    With Mono you can use that effort on Linux, Android, iOS and MacOS.

    Which makes it a great cross-platform option - each of these platforms uses C# and a lot of core .NET classes but provides direct bindings to the native UI controls etc. and then compiles to native code.

    Damien Guard 10 November 2012
  31. Avatar for Adam Ralph

    I didn't know about number 1 - that is very cool!

    I think number 3 is fundamental and is something every C# programmer should know!

    Adam Ralph 10 November 2012
  32. Avatar for Tim Welsh

    Thanks for this! I could use tips like these everyday, and I even fall into the category of devs who knew some of them already. Cheers!

    Tim Welsh 11 November 2012
  33. Avatar for Jerome Jarvis

    Bear in mind once in they’re there until your app quits so use String.Intern carefully.

    Jerome Jarvis 13 December 2012
  34. Avatar for Shaun Wilson

    All things relative, every good C# developer should already know all of this. Excellent C# developers should be chuckling at the few of these 'lesser knowns' which point out a much larger lack of understanding, namely 3, 4, 5, and 7. Similarly, the atomicity of a 64bit (long) assignment can't be guaranteed on a 32bit platform because the underlying hardware requires two MOV ops (or an iterative op that is also not atomic), since .NET targets both 32bit and 64bit platforms a 64bit (long) assignment would be subject to the capability of the underlying hardware. The standard takes this into account and so C# itself does not guarantee atomicity of a 64bit assignment, but as a matter of fact a "x = 1L" (long, 64bit assignment) is an atomic operation on a 64bit Intel-based computer unless you're running your app in "the 32bit WOW". You can verify this by launching your process on a 64bit machine and then inspecting the assembly/machine code (not the IL) to see a 64bit, atomic assignment. This is right up there with people believing that "default(T)" "where T : class" incurs no assignment, when in fact on an Intel CPU a classic mov + xor pair are generated/executed to zero out the variable location in memory (it's no different than assigning null or a 0 value.) A very interesting read, I think 6 is interesting, but I'm also curious if that is standard behavior (ECMA compliant/required) or if it's an implementation-specific by-product of static class initialization in Microsoft's CLR?

    Shaun Wilson 5 April 2013
  35. Avatar for Kip Potter

    #6) The order of static field declaration does not matter unless they are initialized inline, in which case it could be said that static field declaration order does not matter while static field initialization order does matter. Also, it is important to note when static field initialization takes place. According to C# Language Specification Version 5.0 - 10.12, the exact order of initializing static classes is:

    "...first a new set of static fields (§10.5.1) for that particular closed type is created. Each of the static fields is initialized to its default value (§5.2). Next, the static field initializers (§10.4.5.1) are executed for those static fields. Finally, the static constructor is executed."

    Anyways, a side-note regarding an error in the specs, it references §10.4.5.1 as the location of static field initialization, but it's wrong. The correct location is §10.5.5.1 in the Version 5.0 specifications document. The older spec document(s) have it at §10.4.5.1.

    Kip Potter 20 June 2013
  36. Avatar for Mohit Raj

    This is a great list! I have been learning C# for a while and already knew about #5, #7 and #8. The rest was new information for me. Thanks

    Mohit Raj 18 February 2016
  37. Avatar for Alexander Morou

    I suspect #2 was most obvious to me because I wrote a .NET Metadata reader, and I'm now in the process of writing the reverse.

    Lots of work to do so little!

    Alexander Morou 23 June 2016
  38. Avatar for Adriano

    Very interesting post. I knew only half of them. My surprise when I read the spec for first time was when I discovered that the strict rule to name variables states that them must start with '@'. Not start a variable with the at sign is an alias. That is the reason you have to create an anonymous object like { @class="some-css-class" } when you want to set specific class to a filed using razor.

    Adriano 21 November 2016
  39. Avatar for Soner Gönül

    I really like #6. I think this is related to $10.5.5.1 of the C# spec:

    The static field variable initializers of a class correspond to a sequence of assignments that are executed in the textual order in which they appear in the class declaration.

    Soner Gönül 12 March 2017
  40. Avatar for Alexander Derck

    About #5, you say: 'I think enums are evil but at least this lets you centralize some of the switch/if handling and abstract them away a bit until you can do something better. Remember to check the values are in range too.'.

    Would you mind explaining why you consider enums evil? Never heard that one before and can't think of anything myself to be honest.

    Alexander Derck 12 December 2018