Skip to content

narekmalk/c-sharp-questions

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

33 Commits
 
 

Repository files navigation

Tricky C# Questions & Answers

Test your C# knowledge before an interview with these questions of type "what's the output of the following code snippet". In many cases you'll find answers surprising. Please leave a star.


  1. What's the output?
class Foo<T>
{
    public static int Bar;
}

void Main()
{
    Foo<int>.Bar++;
    Console.WriteLine(Foo<double>.Bar);
}
Answer

Output:

0

Explanation:

Foo<int> and Foo<double> are considered two different types due to the generic parameter. So, the Bar of Foo<int> is different from Bar of Foo<double>.


  2. What's the output?
static void Main(string[] args)
{
    Console.WriteLine(Math.Round(6.5));
    Console.WriteLine(Math.Round(11.5));
}
Answer

Output:

6
12

Explanation:

.NET uses "Banker's Rounding" (also known as "Round Half to Even") as the default rounding mechanism. In this method, if the number to be rounded is exactly halfway between two other numbers, it is rounded to the nearest even number. This method is often used in financial calculations to reduce bias.


  3. What's the output?
Console.WriteLine(1 + 2 + 'A');
Console.WriteLine(1 + 'A' + 2);
Console.WriteLine('A' + 1 + 2);
Answer

Output:

68
68
68

Explanation:

When adding types Int32 and Char, conversion of Char to Int32 happens. Thus, in all 3 cases result will be the code of symbol 'A' (65) increased by 3.


  4. What's the output?
static String str;
static DateTime time;

static void Main(string[] args)
{
    Console.WriteLine(str == null ? "str == null" : str);
    Console.WriteLine(time == null ? "time == null" : time.ToString());
}
Answer

Output:

str == null
1/1/0001 12:00:00 AM

Explanation:

Both variables are not initialized, but a string is a reference type and DateTime is a value type. The default value of DateTime type is DateTime.MinValue, which equals 1/1/0001 12:00:00 AM.


  5. What's the output?
var bar = new Bar { Foo = new Foo() };
bar.Foo.Change(5);
Console.WriteLine(bar.Foo.Value);

public struct Foo
{
    public int Value;
    public void Change(int newValue)
    {
        Value = newValue;
    }
}
public class Bar
{
    public Foo Foo { get; set; }
}
Answer

Output:

0

Explanation:

Structs are copied by value, not by reference. When we refer to property bar.Foo, the method bar.get_Foo() is called, which returns us the copy of the structure, thus, the original structure remains unchanged. Then, when calling WriteLine, we again refer to the same property and are printing Value of a new copy of Foo, which is 0.


  6. What's the output?
foreach (Foo current in Baz().ToList())
{
    Console.WriteLine(current.Bar);
}
Console.WriteLine("--delimiter--");
foreach (Foo current in Baz())
{
    Console.WriteLine(current.Bar);
}

IEnumerable<Foo> Baz() 
{
    Foo instance = new Foo();
  
    for (int i=0; i<10; i++)
    {
        instance.Bar++;
        yield return instance;
    }
}

class Foo 
{
    public int Bar {get; set;}
}
Answer

Output:

10
10
10
10
10
10
10
10
10
10
--delimiter--
1
2
3
4
5
6
7
8
9
10

Explanation:

In the first loop we turn IEnumerable to List, so we end up with a list of references to the same object. In the second loop we print the value of Bar as we iterate over the IEnumerable.


  7. What's the output?
var list = new List<string> { "Foo", "Bar", "Baz" };
var startLetter = "F";
var query = list.Where(c => c.StartsWith(startLetter));
startLetter = "B";
query = query.Where(c => c.StartsWith(startLetter));
Console.WriteLine(query.Count());
Answer

Output:

2

Explanation:

Because of deferred execution, queries are executed only at the point query.Count() is called. At this point, both filters are applied with startLetter set to "B".


  8. What's the output?
class A
{
    public void Abc(int q)
    {
        Console.WriteLine("Abc from A");
    }
}

class B : A
{
    public void Abc(double p)
    {
        Console.WriteLine("Abc from B");
    }
}

static void Main(string[] args)
{
    int i = 5;
    B b = new B();
    b.Abc(i);
}
Answer

Output:

Abc from B

Explanation:

Contrary to Java, in C# a class is defined as a component that attempts to be self-sufficient whenever possible. So, the compiler first is looking at the class itself and attempts to resolve a symbol that is requested.


  9. What's the output?
delegate void SomeMethod();

static void Main(string[] args)
{
    List<SomeMethod> delList = new List<SomeMethod>();
    for (int i = 0; i < 10; i++)
    {
        delList.Add(delegate { Console.WriteLine(i); });
    }

    foreach (var del in delList)
    {
        del();
    }
}
Answer

Output:

10
10
10
10
10
10
10
10
10
10

Explanation:

The tricky part here is understanding closures in C#. When the delegate is created, a closure is created, which captures the variable i by reference, not by value. This means that when the delegate is invoked, it will use the value of i at the time of invocation (which is 10), not the value of i at the time the delegate was created. If you wanted each delegate to remember the value of i at the time it was created, you would need to create a temporary variable inside the loop, assign i to it, and use that in the delegate.


  10. What's the output?
static void Main(string[] args)
{
    string hello = "hello";
    string helloWorld = "hello world";
    string helloWorld2 = "hello world";
    string helloWorld3 = hello + " world";

    Console.WriteLine(helloWorld == helloWorld2);
    Console.WriteLine(object.ReferenceEquals(helloWorld, helloWorld2));
    Console.WriteLine(object.ReferenceEquals(helloWorld, helloWorld3));
}
Answer

Output:

True
True
False

Explanation:

  1. This line checks if the values of helloWorld and helloWorld2 are equal.
  2. This line checks if helloWorld and helloWorld2 refer to the exact same object. In C#, the .NET runtime performs a process called string interning for literal strings. This means that when you have two or more identical string literals in your code, the runtime only creates one string object, and all variables that are assigned that string literal actually refer to the same object.
  3. In this line, helloWorld3 is created by concatenating hello and " world", which creates a new string object. So even though the value of helloWorld3 is also "hello world", it's not the same object as helloWorld.


  11. What's the output?
public class TestStatic {
    public static int TestValue;

    public TestStatic() {
        if (TestValue == 0) {
            TestValue = 5;
        }
    }
    static TestStatic() {
        if (TestValue == 0) {
            TestValue = 10;
        }
    }

    public void Print() {
        if (TestValue == 5) {
            TestValue = 6;
        }

        Console.WriteLine("TestValue : " + TestValue);
    }
}

public void Main(string[] args) {
    TestStatic t = new TestStatic();
    t.Print();
}
Answer

Output:

TestValue : 10

Explanation:

In C#, a static constructor (also called a type initializer) is called automatically before the first instance is created or any static members are referenced. During the execution of the static constructor, TestValue is 0, so it is set to 10. After that, the instance constructor is run, and than t.Print() is called, but these methods don't change TestValue.


  12. What's the output?
using System.Threading.Tasks;

public static void Main(string[] args)
{
    Console.WriteLine("Main start");
    Method1();
    Console.WriteLine("Main end");
    Console.ReadLine();
}

public static async void Method1()
{
    Console.WriteLine("Method1 start");
    await Method2();
    Console.WriteLine("Method1 end");
}

public static async Task Method2()
{
    Console.WriteLine("Method2 start");
    await Task.Delay(1000);
    Console.WriteLine("Method2 end");
}
Answer

Output:

Main start
Method1 start
Method2 start
Main end
Method2 end
Method1 end

Explanation:

The Main method prints "Main start".
Next, Method1() is called. This is an async method which starts by printing "Method1 start".
Method1() then calls Method2() using await, meaning it will asynchronously wait for Method2() to complete before it continues. Method2() is an async method that returns a Task. It starts by printing "Method2 start".
Inside Method2(), we call await Task.Delay(1000). This will pause Method2() for 1 second, during which control is returned back to the calling method.
Since we are awaiting Method2() in Method1(), control is returned back to the Main method. The "Main end" message is printed.
The Console.ReadLine() at the end of Main method keeps the program running, which allows us to see the delayed output from the other methods.
After the delay in Method2(), it prints "Method2 end" and completes.
Since Method1() was awaiting Method2(), after Method2() completes, control is returned back to Method1() which then prints "Method1 end" and completes.


  13. What's the output?
using System;
using System.Reflection;

public class Example
{
    public readonly int ReadOnlyField;

    public Example(int value)
    {
        ReadOnlyField = value;
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        Example example = new Example(10);
        Console.WriteLine($"Before applying reflection: {example.ReadOnlyField}");

        // Applying reflection to change the value of readonly field
        var field = typeof(Example).GetField("ReadOnlyField");
        field.SetValue(example, 20);

        Console.WriteLine($"After applying reflection: {example.ReadOnlyField}");
    }
}
Answer

Output:

Before applying reflection: 10
After applying reflection: 20

Explanation:

It's possible to use Reflection to change the value of a readonly field, though doing so can lead to unexpected behavior and is generally not recommended as it violates the principle of immutability that readonly keyword is meant to enforce.


  14. What's the output?
var s = new S();

using (s)
{
    Console.WriteLine(s.GetDispose());
}
Console.WriteLine(s.GetDispose());

public struct S : IDisposable
{
    private bool dispose;
    public void Dispose()
    {
        dispose = true;
    }
    public bool GetDispose()
    {
        return dispose;
    }
}
Answer

Output:

False
False

Explanation:

using makes a copy of the value type, and you are therefore disposing a copy, not the original. More details: https://ericlippert.com/2011/03/14/to-box-or-not-to-box/


  15. What's the output?
class Program
{
    private static Object syncObject = new Object();
    private static void Write()
    {
        lock (syncObject)
        {
            Console.WriteLine("test");
        }
    }
    static void Main(string[] args)
    {
        lock (syncObject)
        {
            Write();
        }
    }
}
Answer

Output:

test

Explanation:

The Write method attempts to acquire a lock on the same syncObject. Normally, this could be a problem—if another thread were involved, it would result in a deadlock because the Main method already holds the lock. However, in this case, since there are no multiple threads involved, the lock is re-entrant. A re-entrant lock means that the same thread can acquire the same lock multiple times without causing a deadlock. It already holds the lock, so it is allowed to proceed.


  16. What's the output?
int a = 0;
int Foo()
{
    a = a + 42;
    return 1;
}
void Main()
{
    a += Foo();
    Console.WriteLine(a);
}
Answer

Output:

1

Explanation:

a += Foo() will be converted to a = a + Foo(). Firstly, left operand will be evaluated, which is 0, then, right operand will be evaluated, which returns 1, so the result will be 1, despite the fact that a is reassigned inside Foo().


  17. What's the output?
static void Main(string[] args)
{
    object sync = new object();
    var thread = new Thread(()=>
    {
        try
        {
            Work();
        }
        finally
        {
            lock (sync)
            {
                Monitor.PulseAll(sync);
            }
        }
    });
    thread.Start();
    lock (sync)
    {
        Monitor.Wait(sync);
    }
    Console.WriteLine("test");
}
private static void Work()
{
    Thread.Sleep(1000);
}
Answer

Output:

test

Explanation:

When calling Monitor.Wait(sync), sync object is released before waiting for pulsing. Therefore, the thread is able to enter lock block and call Monitor.PulseAll(sync), which awakens the main thread.


  18. What's the output?
void Foo(object a)
{
    Console.WriteLine("object");
}
void Foo(object a, object b)
{
    Console.WriteLine("object, object");
}
void Foo(params object[] args)
{
    Console.WriteLine("params object[]");
}
void Foo<T>(params T[] args)
{
    Console.WriteLine("params T[]");
}
class Bar { }
void Main()
{
    Foo();
    Foo(null);
    Foo(new Bar());
    Foo(new Bar(), new Bar());
    Foo(new Bar(), new object());
}
Answer

Output:

params object[]
params object[]
params T[]
params T[]
object, object

Explanation:

Let's look at each case.

Foo() -> params object[]
Versions object, object, object are not suitable because of number of arguments. Version params T[] is not suitable, because T can't be resolved. Thus, correct version is params object[].

Foo(null) -> params object[]
Version object, object is not suitable because of number of arguments. Version params T[] is not suitable, because T can't be resolved. From the two remaining versions compiler will choose params object[] - I don't know why :)

Foo(new Bar()) -> params T[]
Version object, object is not suitable because of number of arguments. Versions object and params object[] will require additional cast of Bar to object, thus, params T[] is more preferrable.

Foo(new Bar(), new Bar()) -> params T[]
Version object is not suitable because of number of arguments. Versions object, object and params object[] will require additional cast of Bar to object, thus, params T[] is more preferrable.

Foo(new Bar(), new object()) -> object, object
Version object is not suitable because of number of arguments. Among the remaining versions object, object is the most specific one.


  19. What's the output?
IEnumerable<string> Foo()
{
    yield return "Bar";
    Console.WriteLine("Baz");
}
void Main()
{
    foreach (var str in Foo())
        Console.WriteLine(str);
}
Answer

Output:

Bar
Baz

Explanation:

First iteration of foreach yield returns "Bar" which is written to console. Second iteration tries to find another yield return (meanwhile writing to console "Baz") but finds nothing and the loop ends.


  20. What's the output?
var x = "AB";
var y = new System.Text.StringBuilder().Append('A').Append('B').ToString();
var z = string.Intern(y);
Console.WriteLine(x == y);
Console.WriteLine(x == z);
Console.WriteLine((object)x == (object)y);
Console.WriteLine((object)x == (object)z);
Answer

Output:

True
True
False
True

Explanation:

First two will print True, because we are comparing by value. Next two are comparing by reference. x points to interned string "AB" as it's declared with string literal. z points to the same interned string as it's defined with method string.Intern. But y will point to other memory location as it's defined with StringBuilder, which doesn't consider interned strings when calling ToString().


  21. What's the output?
try
{
    Console.WriteLine(((string)null + null + null) == "");
}
catch (Exception e)
{
    Console.WriteLine(e.GetType());
}
Answer

Output:

True

Explanation:

If an operand of string concatenation is null, an empty string is substituted.




Sources

About

Tricky C# questions and answers

Topics

Resources

Stars

Watchers

Forks