The Vital Guide to C# Interviewing
Remember the Y2K bug? Apparently, the world was supposed to end due to havoc in computer networks all over the planet. Just one year before that, we heard announcements that 1999 will mark the end of the world because it had an inverted 666 in its name. In such turbulent times, when many were piling food in their basements, hoping that Armageddon would not find them, there were a few visionaries that were still inventing some cool stuff. One of them is Anders Hejlsberg, who gathered a team of Microsoft developers and created Cool, which was the first version of new C-like Object Oriented Language, later to be named C#.
14 years and 5 versions later, C# is one of the most popular languages in the industry. Despite the fact that James Gosling and Bill Joy stated that C# was just an imitation of Java, while some other critics even called C# a boring repetition that lacks innovation, C# is now standing tall next to all the other platforms used by millions of developers all over the world.
C# is a multi-paradigm programming language encompassing strong typing, as well as imperative, declarative, functional, generic, object-oriented (class-based), and component-oriented programming disciplines. You can use it to build any type of application, whether it is a service, console, desktop, web or even smartphone application.
Each application type requires a specific set of skills on top of standard C# syntax, and finding a great C# developer is not an easy task. If you are looking for a web developer you should expect knowledge of the HTTP protocol, Web Forms, MVC Framework and Razor View Engine, while some other application will have its own challenges.
This article should help you identify a developer that understands C# in its core. Regardless on the application type, techniques and tips mentioned below should be universal to all C# developers and they should all be able to demonstrate extensive understanding of these topics.
I’ll try to cover general topics that every developer must be aware of. The purpose of this article is to point to several specific topics. Evaluating knowledge of each of them in depth would require much more than one or two questions.
Generics
Generics were one of the earliest features of the C# language. Generics make it possible to design classes and methods that defer the specification of one or more types until the class or method is declared and instantiated by client code.
Q: Consider the following code, which is part of a console application:
static void Main(string[] args) { DeveloperList listOfDevelopers = new DeveloperList(); IntegerList listOfNumbers = new IntegerList(); listOfNumbers.DoSomething(5); listOfDevelopers.DoSomething(new Developer()); } public class Developer { public string Name { get; set; } public List<string> Skills { get; set; } } public class DeveloperList { public void DoSomething(Developer developer) { Console.WriteLine("Doing something with developer"); } } public class IntegerList { public void DoSomething(Int32 number) { Console.WriteLine("Doing something with number"); } }
Optimize the code in a way to replace DeveloperList
and IntegerList
with one class named GenericList
that will contain single doSomething
method. Make sure to handle the case when GenericList
is instantiated by an unexpected type.
The solution should be similar to this:
static void Main(string[] args) { GenericList<int> listOfNumbers = new GenericList<int>(); GenericList<Developer> listOfDevelopers = new GenericList<Developer>(); GenericList<string> listOfStrings = new GenericList<string>(); listOfNumbers.DoSomething(5); listOfDevelopers.DoSomething(new Developer()); listOfStrings.DoSomething("Whats up"); } public class Developer { public string Name { get; set; } public List<string> Skills { get; set; } } class GenericList<T> { public void DoSomething(T value){ if (value.GetType() == typeof(Int32)) { Console.WriteLine("Doing something with "); return; } if (value.GetType() == typeof(Developer)) { Console.WriteLine("Doing something with developer"); return; } Console.WriteLine("I cannot do anything with " + value.GetType().ToString()); } }
LINQ
LINQ (Language-Integrated Query) is one of the coolest features in C#. It was introduced in Visual Studio 2008 and it provides extremely powerful query capabilities to C#. LINQ introduces standard patterns for querying and updating data supporting any kind of data store.
Or, in simple words, LINQ enables SQL-like queries against collections of objects in C#.
Q: Assuming that you have a Class Developer
defined like this:
class Developer { public string Name { get; set; } public List<string> Skills { get; set; } }
Write a code that would extract all developers that have the SQL skill, from a List<Developer> developers
.
The answer that you would expect from a developer that understands LINQ would be similar to this:
var results = from d in developers where d.Skills.Contains("SQL") select d;
Developers that are not used to LINQ would probably use standard iteration methods using for
, foreach
or while
combined with if
. Similar to this:
var results = new List<Developer>(); foreach (var d in developers) { if (d.Skills.Contains("SQL")) results.Add(d); }
Even though this is a technically correct solution, it would be less desirable due to the complexity of the code.
Lambda Expressions
Lambda expressions are methods that do not require declaration. These are function that are implemented in-line with the rest of the code. Lambda expressions are particularly helpful for writing LINQ query expressions.
To create a lambda expression, you specify input parameters (if any) on the left side of the lambda operator =>, and you put the expression or statement block on the other side. For example, the lambda expression x => x * x
specifies a parameter named x
and returns the value of x
squared.
The general definition of lambda expression is:
(parameters) => Code
Q: Complete the following code by implementing the methods Square
and Double
based on the Calculate
delegate and lambda expressions.
delegate int Calculate(int input); static void Main(string[] args) { int value1 = Square(5); int value2 = Double(5); Console.WriteLine(value1); Console.WriteLine(value2); Console.ReadLine(); }
The expected solution should be as simple as adding two lines of code:
delegate int Calculate(int input); static void Main(string[] args) { Calculate Square = x => x * x; // NEW Calculate Double = x => x * 2; // NEW int value1 = Square(5); int value2 = Double(5); Console.WriteLine(value1); Console.WriteLine(value2); Console.ReadLine(); }
The Calculate
delegate is declared to return Int
and accept one Int
as a parameter, so Square
and Double
methods just implemented the proper calculations.
Named Arguments
Named arguments free you from the need to remember or to look up the order of parameters in the parameter lists of called methods. The parameter for each argument can be specified by parameter name.
Consider the following code:
static void Main(string[] args) { Console.WriteLine(Power(4, 3)); //64 Console.WriteLine(Power(3, 4)); //81 } static double Power(double baseNumber, double power) { return Math.Pow(baseNumber, power); }
Changing the order of parameters passed to the Power
method would produce a different result; 64 one way, versus 81 the other.
Q: Update the code above to produce same result (4 to power of 3 = 64) regardless of the order of parameters passed to the Power
method.
The solution for this problem is to pass named parameters. You would have to change the way you call Power
.
Console.WriteLine(Power(baseNumber: 4, power: 3)); //64 Console.WriteLine(Power(power: 3, baseNumber: 4)); //64
The only thing you needed to do is add an argument name when passing in the values.
Optional Parameters
The definition of a method, constructor, indexer, or delegate can specify that its parameters are required or that they are optional. Any call must provide arguments for all required parameters, but can omit arguments for optional parameters.
Each optional parameter has a default value as part of its definition. If no argument is sent for that parameter, the default value is used. Optional parameters are defined at the end of the parameter list, after any required parameters. If the caller provides an argument for any one of a succession of optional parameters, it must provide arguments for all preceding optional parameters. Comma-separated gaps in the argument list are not supported.
Q: Extend the Developer
class with a boolean property named Enabled
and a constructor that will accept name
and the optional enabled
value.
public class Developer { public string Name { get; set; } public List<string> Skills { get; set; } }
The expected solution would be:
public class Developer { public Developer(string name, bool enabled=true) { Name = name; Enabled = enabled; } public string Name { get; set; } public bool Enabled { get; set; } public List<string> Skills { get; set; } }
Use of the optional enabled
parameter is shown in the following example:
Developer elvis = new Developer("Elvis"); //elvis.Enabled = true Developer mick = new Developer("Mick", false); //mick.Enabled = false
Asynchronous Processing
Asynchrony is essential for activities that are potentially blocking, such as when your application accesses network resources. Access to a network resource sometimes is slow or delayed. If such an activity is blocking a synchronous process, the entire application must wait. In an asynchronous process, the application can continue with other work that doesn’t depend on the network resource until the potentially blocking task finishes.
Visual Studio 2012 introduced a simplified approach, async programming, that leverages asynchronous support in the .NET Framework 4.5 and the Windows Runtime. The compiler does the difficult work that the developer used to do, and your application retains a logical structure that resembles synchronous code. As a result, you get all the advantages of asynchronous programming with a fraction of the effort.
The async
and await
keywords in C# are the heart of async programming. By using those two keywords, you can use resources in the .NET Framework or the Windows Runtime to create an asynchronous method almost as easily as you create a synchronous method. Asynchronous methods that you define by using async
and await
are referred to as async methods.
The following example shows an async method.
async Task<int> AccessTheWebAsync() { HttpClient client = new HttpClient(); Task<string> getStringTask = client.GetStringAsync("https://www.toptal.com"); DoIndependentWork(); string urlContents = await getStringTask; return urlContents.Length; }
Q: What would be the output of the following code? Explain your answer.
static string statement = "Start"; static async Task<string> SayAwaited() { await Task.Delay(5000); statement = "Awaited"; Console.WriteLine(statement); return statement; } static async Task<string> SayDelayed() { Thread.Sleep(1000); statement = "Delayed"; Console.WriteLine(statement); return statement; } static void Main(string[] args) { SayAwaited(); SayDelayed(); Console.WriteLine("Final: " + statement); Console.ReadLine(); }
After five seconds of the application being paused, the output of this code would be:
Delayed Final: Delayed Awaited
Note: The first Delayed will show up after a delay of one second.
The reason for this is in the way async
methods are handled in C#, and the difference between Task.Delay
and Thread.Sleep
. The SayAwaited
method will execute like any other method until it reaches the await
command. At this point, C# will start another thread for executing Task.Delay
on the new thread, while releasing current thread to proceed with next action, which is SayDelayed
. After putting the main thread to sleep for one second, SayDelayed
will set the value for the statement
and execution will proceed with the remaining commands, WriteLine
and ReadLine
. While this is all happening, SayAwaited
will be nicely paused in its own thread, and after 5 seconds it will set statement
to Awaited, produce its own output, and return.
Q: Would there be any difference if statement = "Awaited"
would be execute before await
command? Explain your answer.
static async Task<string> SayAwaited() { statement = "Awaited"; await Task.Delay(5000); Console.WriteLine(statement); return statement; }
In this case, the output would be the following:
Delayed Final: Delayed Delayed
The reason for this is that the Awaited value is assigned to statement
before the await
command is called. By the time Console.WriteLine(statement);
in SayAwaited
is executed, the value of statement
has already been updated by the SayDelayed
method.
Stay Sharp!
C# came in as a new kid on the block. At first it was ignored, then it was ridiculed, then it was fought against. Now it continues to win over developers all over the world. The direct result of this growth is the large number of developers that are using C#.
This article referenced topics that every C# developer should master. Make sure to cover these topic when you are looking for a great C# developer, and you will be one step closer to identifying the best of the best.