Top C# Interview Questions

C#StackOverflowErrorOutOfMemoryError
- StackOverflowError: Occurs from excessive recursion, stack memory exhausted.
- OutOfMemoryError: Occurs when heap memory is exhausted.
Example:
void Recursive() { Recursive(); } // StackOverflow

C#Short-Circuit EvaluationOperators
Short-circuit evaluation skips evaluating parts of a logical expression if the outcome is determined (e.g., `&&`, `||`).
Example:
bool b = false && SomeMethod(); // SomeMethod not called

C#Lazy InitializationPerformance
Use `Lazy` for expensive objects, initialize only when needed, and ensure thread safety with appropriate `LazyThreadSafetyMode`.
Example:
Lazy> lazyList = new Lazy>(() => new List());

C#Ternary SearchAlgorithms
Ternary search divides a sorted array into three parts, recursively narrowing the search space, less efficient than binary search.

C#Circular ReferenceDependency Management
Use interfaces, dependency injection, or redesign to break circular dependencies.
Example:
interface IComponent {
    void Act();
}
class ComponentA {
    private readonly IComponent b;
    public ComponentA(IComponent b) => this.b = b;
}

C#Async/AwaitAsynchronous Programming
`async`/`await` enables non-blocking asynchronous code, where `await` pauses execution until a task completes.
Example:
async Task Method() {
    await Task.Delay(1000);
}

C#IEnumerableWhere MethodInterfaces
Implement `IEnumerable` to enable LINQ's `Where` method.
Example:
class MyList : IEnumerable {
    public IEnumerator GetEnumerator() => new List().GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

C#MarshallingInterop
Marshalling converts data between managed and unmanaged code, needed for interop with native code.
Example:
[DllImport("user32.dll")]
static extern void MessageBox();

C#Late BindingEarly BindingPolymorphism
- Early Binding: Type resolved at compile-time (faster).
- Late Binding: Type resolved at runtime (e.g., `dynamic`).
Example:
dynamic d = 10;
d = "Hello"; // Late binding

C#MemberwiseCloneObject Cloning
`MemberwiseClone()` creates a shallow copy of an object, copying fields but not nested objects.
Example:
class MyClass {
    public int X;
}
MyClass c1 = new MyClass { X = 10 };
MyClass c2 = (MyClass)c1.MemberwiseClone();

C#Preprocessor DirectivesConditional Compilation
Conditional directives (`#if`, `#endif`) include/exclude code based on symbols.
Example:
#if DEBUG
Console.WriteLine("Debug mode");
#endif

C#LambdasDelegates
- Delegate: Type-safe function pointer.
- Lambda: Concise syntax for creating delegate instances.
Example:
delegate int D(int x);
D d = x => x * x;

C#Deferred ExecutionLINQ
Deferred execution delays query execution until results are needed, improving performance and enabling query composition.
Example:
var numbers = new[] { 1, 2, 3 }.Where(n => n > 1); // Not executed yet

C#FinalizeDisposeResource Management
- Dispose: Deterministic cleanup for managed/unmanaged resources.
- Finalize: Non-deterministic, for unmanaged resources only.
Example:
class Resource : IDisposable {
    public void Dispose() { }
    ~Resource() { }
}

C#Static ConstructorsClass Initialization
Static constructors initialize static members, called automatically before first use.
Example:
class MyClass {
    static MyClass() { }
}

C#IEnumerableIQueryableCollections
- IEnumerable: In-memory, executes locally.
- IQueryable: Queryable, executes on the data source (e.g., database).
Example:
IEnumerable e = new List();
IQueryable q = e.AsQueryable();

C#IQueryableICollectionIListIDictionaryInterfaces
- IQueryable: For queryable data sources.
- ICollection: Basic collection operations.
- IList: Indexed collection with add/remove.
- IDictionary: Key-value pairs.
Example:
IList list = new List();
IDictionary dict = new Dictionary();

C#IEqualityComparerCustom Equality
An `IEqualityComparer` defines custom equality and hash code logic for a type, used by collections like `Dictionary` or LINQ methods (e.g., `Distinct`, `GroupBy`).

Why Needed:
- To compare objects based on specific properties (e.g., case-insensitive strings).
- To customize equality for complex types without modifying their `Equals` or `GetHashCode`.
- To optimize collection operations by providing domain-specific equality logic.

Implementation:
- Implement `Equals(T x, T y)` to compare two objects for equality.
- Implement `GetHashCode(T obj)` to return a consistent hash code for equal objects.
- Ensure `GetHashCode` is stable and aligns with `Equals` to avoid collection errors.

Use Cases:
- Case-insensitive key lookups in a dictionary.
- Grouping objects by a subset of properties in LINQ queries.
Example:
using System;
using System.Collections.Generic;

public class CaseInsensitiveComparer : IEqualityComparer
{
    public bool Equals(string x, string y)
    {
        if (x == null && y == null) return true;
        if (x == null || y == null) return false;
        return x.Equals(y, StringComparison.OrdinalIgnoreCase);
    }

    public int GetHashCode(string obj)
    {
        if (obj == null) return 0;
        return obj.ToLowerInvariant().GetHashCode();
    }
}

class Program
{
    static void Main()
    {
        var dict = new Dictionary(new CaseInsensitiveComparer())
        {
            { "Key", "Value" },
            { "KEY", "NewValue" } // Overwrites due to case-insensitive comparison
        };
        Console.WriteLine(dict.Count); // 1
        Console.WriteLine(dict["key"]); // NewValue

        // LINQ Example
        var names = new[] { "Alice", "alice", "Bob" };
        var distinctNames = names.Distinct(new CaseInsensitiveComparer());
        Console.WriteLine(string.Join(", ", distinctNames)); // Alice, Bob
    }
}

C#Dependency Injection.NET CoreCustom Lifetime
.NET Core’s dependency injection (DI) is built into the framework via the `Microsoft.Extensions.DependencyInjection` package, providing a lightweight, extensible DI container.

How DI Works:
- Services are registered in the `IServiceCollection` during application startup (e.g., in `Program.cs`).
- Common lifetimes include:
- Transient: New instance per request.
- Scoped: Same instance within a scope (e.g., HTTP request).
- Singleton: Same instance for the application’s lifetime.
- The container resolves dependencies when constructing objects, injecting them via constructors, properties, or methods.
- Middleware, controllers, and services can consume registered services via constructor injection.

Custom Service Lifetime:
- Implement a custom lifetime by creating a class that implements `IServiceProvider` or extends `ServiceLifetime`.
- Use a factory pattern or custom scope management to control instance creation and disposal.
- Register the custom lifetime with a descriptor in `IServiceCollection`.

Use Cases:
- Use DI for loose coupling, testability, and modularity.
- Custom lifetimes are rare but useful for specialized scenarios like per-user caching or connection pooling.
Example:
using Microsoft.Extensions.DependencyInjection;
using System;

public interface IMyService { string GetData(); }
public class MyService : IMyService, IDisposable
{
    public string GetData() => "Service Data";
    public void Dispose() => Console.WriteLine("Service Disposed");
}

// Custom Lifetime
public class CustomLifetime : IServiceLifetime
{
    private readonly Func _factory;
    private object _instance;

    public CustomLifetime(Func factory) => _factory = factory;

    public object GetService()
    {
        _instance ??= _factory();
        return _instance;
    }
}

class Program
{
    static void Main()
    {
        var services = new ServiceCollection();

        // Standard DI
        services.AddScoped();

        // Custom Lifetime
        services.Add(new ServiceDescriptor(
            typeof(IMyService),
            _ => new MyService(),
            ServiceLifetime.Scoped)); // Mimics Scoped for demonstration

        var provider = services.BuildServiceProvider();

        using (var scope = provider.CreateScope())
        {
            var service = scope.ServiceProvider.GetService();
            Console.WriteLine(service.GetData()); // Service Data
        } // Disposed here
    }
}

C#Garbage CollectionPerformance Optimization
.NET’s garbage collector (GC) automatically reclaims memory for unused objects using a mark-and-sweep algorithm with generational collection.

How it Works:
- The GC identifies live objects (reachable from roots like stack, static fields) and marks them.
- Unmarked objects are swept, and memory is compacted to reduce fragmentation.
- Generations:
- Gen 0: Short-lived objects (e.g., locals). Collected frequently, fast due to small size.
- Gen 1: Objects surviving Gen 0, a buffer between Gen 0 and Gen 2.
- Gen 2: Long-lived objects (e.g., statics). Collected infrequently, slower due to size.
- The GC triggers collections based on memory pressure or allocation thresholds, with higher generations requiring more time.

Strategies to Minimize GC Impact:
- Reduce Allocations: Use value types (structs) for small data, Span, or object pooling for reusable objects.
- Avoid Large Object Heap (LOH): Keep objects <85KB to avoid LOH, which is costly to collect.
- Use Weak References: Allow GC to collect objects when memory is low.
- Dispose Resources: Implement IDisposable for unmanaged resources and use `using` statements.
- Profile GC: Use PerfView or dotMemory to monitor allocations and collections.
- Tune GC: Configure GC modes (e.g., Server GC for high-throughput apps) via runtimeconfig.json.

Use Cases: Optimize for real-time systems, games, or high-throughput APIs where GC pauses are critical.
Example:
using System;
using System.Buffers;

class Program
{
    static void Main()
    {
        // Minimize allocations with Span
        Span buffer = stackalloc byte[100];
        buffer[0] = (byte)'A';
        Console.WriteLine((char)buffer[0]); // A

        // Object pooling
        var pool = ArrayPool.Shared;
        var pooledBuffer = pool.Rent(1024);
        try
        {
            pooledBuffer[0] = (byte)'B';
            Console.WriteLine((char)pooledBuffer[0]); // B
        }
        finally
        {
            pool.Return(pooledBuffer);
        }

        // Weak reference
        var weakRef = new WeakReference(new object());
        GC.Collect();
        Console.WriteLine(weakRef.TryGetTarget(out var _)); // False (collected)
    }
}

C#Async/AwaitInternal WorkingPitfalls
The async/await pattern in C# simplifies asynchronous programming by allowing developers to write asynchronous code that resembles synchronous code. Internally, it relies on a state machine generated by the compiler.

How it Works:
- When a method is marked with `async`, the C# compiler transforms it into a state machine. This state machine manages the method’s execution, including suspensions and resumptions at `await` points.
- The `await` keyword pauses the method’s execution until the awaited task completes, yielding control back to the caller. The state machine captures the method’s state (local variables, etc.) and schedules a continuation to resume execution.
- The method returns a `Task` or `Task`, which represents the asynchronous operation.
- The `SynchronizationContext` or `TaskScheduler` determines where continuations run (e.g., UI thread for GUI apps).

Common Pitfalls:
- Deadlocks: Calling `.Result` or `.Wait()` on a `Task` in a context with a `SynchronizationContext` (e.g., UI thread) can cause deadlocks if the task needs the same context to complete.
- Overhead: Async state machines add memory and CPU overhead, especially for small tasks.
- Missing ConfigureAwait: Not using `ConfigureAwait(false)` in library code can lead to unnecessary context switching, impacting performance.
- Swallowing Exceptions: Failing to handle exceptions in async methods can lead to unobserved task exceptions.
- Thread Pool Starvation: Excessive use of `Task.Run` for CPU-bound work can exhaust ThreadPool threads.

Mitigation:
- Use `ConfigureAwait(false)` in library code to avoid capturing the context.
- Avoid blocking calls (`.Result`, `.Wait()`); use `await` consistently.
- Handle exceptions with try-catch or `Task.ContinueWith` for fault tolerance.
- Use `ValueTask` for high-performance scenarios with likely synchronous completion.
- Monitor ThreadPool usage with diagnostics tools like PerfView.
Example:
using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        try
        {
            string result = await DoWorkAsync().ConfigureAwait(false);
            Console.WriteLine(result);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }

        // Avoid: Deadlock-prone
        // string badResult = DoWorkAsync().Result; // Can deadlock in UI context
    }

    static async Task DoWorkAsync()
    {
        await Task.Delay(1000).ConfigureAwait(false); // Simulate async work
        return "Completed";
    }
}

C#SpanMemoryHigh Performance
Span and Memory are modern .NET types designed for high-performance scenarios, particularly for reducing allocations and improving memory efficiency.

- Span:
- Represents a contiguous region of memory (e.g., array segment, stack-allocated memory).
- Provides a safe, bounds-checked view over memory without copying.
- Role: Used for low-level, performance-critical operations like parsing strings, processing buffers, or working with stack-allocated memory.
- Constraints: Cannot be used in async methods or stored in fields due to its stack-only nature (ref struct).

- Memory:
- Represents a view over a memory region (e.g., array, string) that can be heap-allocated.
- Supports slicing and can be used in async contexts or stored in objects.
- Role: Used for scenarios requiring memory management across method boundaries, such as asynchronous I/O or long-lived buffers.

- Differences:
- Span is stack-only (ref struct), while Memory is heap-friendly.
- Span is more performant for short-lived operations; Memory is versatile for broader use.
- Span can be created from Memory via `.Span` but not vice versa.

Use Cases:
- Use Span for parsing or processing in tight loops (e.g., JSON parsing).
- Use Memory for async I/O or passing memory across methods (e.g., network streams).
Example:
using System;
using System.Buffers;
using System.Text;

class Program
{
    static void Main()
    {
        // Span: Parse string without allocation
        ReadOnlySpan text = "Hello, World!".AsSpan();
        var commaIndex = text.IndexOf(',');
        var firstPart = text.Slice(0, commaIndex);
        Console.WriteLine(firstPart.ToString()); // Hello

        // Memory: Async buffer
        var buffer = new byte[1024].AsMemory();
        ProcessBuffer(buffer);
    }

    static void ProcessBuffer(Memory buffer)
    {
        Span span = buffer.Span;
        span[0] = (byte)'A';
        Console.WriteLine(Encoding.ASCII.GetString(span.Slice(0, 1))); // A
    }
}

C#IEnumerableIQueryableListPerformance
IEnumerable, IQueryable, and List are used for working with collections in C#, but they differ in performance, execution model, and use cases.

- IEnumerable:
- Represents an in-memory sequence with deferred (lazy) execution.
- Processed using LINQ-to-Objects, which evaluates queries eagerly when enumerated.
- Performance: Suitable for small datasets but can be inefficient for complex queries due to in-memory processing.
- Usage: Use for in-memory collections (e.g., lists, arrays) or simple transformations.

- IQueryable:
- Represents a queryable data source (e.g., database) with deferred execution.
- Queries are translated to provider-specific formats (e.g., SQL for EF Core), executed on the data source.
- Performance: More efficient for remote data as it minimizes data transfer by pushing queries to the server.
- Usage: Use for database queries or remote APIs to leverage server-side optimizations.

- List:
- A concrete, in-memory collection with eager execution.
- Stores data in a contiguous array, offering fast indexing and modification.
- Performance: Fast for direct access and mutations but requires full materialization upfront.
- Usage: Use when you need a mutable, in-memory collection or immediate data access.

Key Considerations:
- IEnumerable is flexible but can lead to multiple enumerations, impacting performance.
- IQueryable optimizes remote queries but requires a compatible provider.
- List is best for small, static datasets requiring frequent modifications.
Example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

class Program
{
    static async Task Main()
    {
        // List: Eager, in-memory
        var list = new List { 1, 2, 3, 4, 5 };
        var evenList = list.Where(n => n % 2 == 0).ToList();
        Console.WriteLine("List: " + string.Join(", ", evenList)); // 2, 4

        // IEnumerable: Lazy, in-memory
        IEnumerable numbers = new List { 1, 2, 3, 4, 5 };
        var evenNumbers = numbers.Where(n => n % 2 == 0);
        Console.WriteLine("IEnumerable: " + string.Join(", ", evenNumbers)); // 2, 4

        // IQueryable: Lazy, remote
        var dbContext = new MyDbContext();
        IQueryable query = dbContext.Users.Where(u => u.Age > 18);
        var adults = await query.ToListAsync();
        Console.WriteLine("IQueryable: " + adults.Count);
    }
}

class MyDbContext : DbContext
{
    public DbSet Users { get; set; }
}
class User { public int Age { get; set; } }

C#Value TuplesAnonymous TypesTuples
Value tuples, introduced in C# 7.0, are lightweight, mutable structs for grouping multiple values without defining a class. They are part of the `System.ValueTuple` family.

- Value Tuples:
- Syntax: `(int, string)` or `(int Id, string Name)` with named elements.
- Mutable, stack-allocated (value type), and lightweight.
- Support deconstruction and comparison operators.
- Usage: Temporary data grouping, multiple return values from methods.

- Comparison:
- Anonymous Types:
- Immutable, heap-allocated, compiler-generated classes.
- Used in LINQ queries or for ad-hoc data shaping.
- Limited to method scope and lack named tuple-like syntax.
- Drawback: Cannot be returned from methods or passed across assemblies.
- Traditional Tuples (System.Tuple):
- Immutable, heap-allocated reference types.
- Verbose syntax (e.g., `Tuple.Create`).
- Less performant due to heap allocation and lack of deconstruction.

Use Cases:
- Use value tuples for multiple return values or lightweight data transfer.
- Use anonymous types for LINQ projections within a method.
- Avoid traditional tuples in modern C# due to performance and usability issues.
Example:
using System;

class Program
{
    static void Main()
    {
        // Value Tuple
        (int Id, string Name) valueTuple = (1, "Alice");
        Console.WriteLine($"Value Tuple: {valueTuple.Id}, {valueTuple.Name}");
        valueTuple.Id = 2; // Mutable
        var (id, name) = valueTuple; // Deconstruction
        Console.WriteLine($"Deconstructed: {id}, {name}");

        // Anonymous Type
        var anon = new { Id = 1, Name = "Bob" };
        Console.WriteLine($"Anonymous: {anon.Id}, {anon.Name}");
        // anon.Id = 2; // Error: Immutable

        // Traditional Tuple
        var tradTuple = Tuple.Create(1, "Charlie");
        Console.WriteLine($"Traditional: {tradTuple.Item1}, {tradTuple.Item2}");
    }

    static (int, string) GetUser() => (1, "Alice");
}

C#Record TypesClassStruct
Record types, introduced in C# 9.0, are specialized types for immutable data modeling, differing from classes and structs in behavior and use cases.

- Record Types:
- Immutable by default (init-only properties), with value-based equality.
- Can be reference (`record class`) or value types (`record struct`).
- Support `with` expressions for non-destructive mutation and automatic `ToString`, `Equals`, and `GetHashCode` implementations.
- Usage: Data transfer objects (DTOs), immutable domain models, or API responses.

- Class:
- Reference types, mutable by default, with reference-based equality.
- Support inheritance and complex behavior.
- Usage: Modeling entities with behavior, stateful services, or objects requiring inheritance.

- Struct:
- Value types, mutable by default, with value-based equality.
- Lightweight, stack-allocated, no inheritance.
- Usage: Small, performance-critical data structures (e.g., coordinates, counters).

Scenarios:
- Use records for immutable data (e.g., configuration, DTOs).
- Use classes for complex objects with behavior (e.g., services, controllers).
- Use structs for small, lightweight data (e.g., Point, DateOnly).
Example:
using System;

// Record
public record Person(string Name, int Age)
{
    public Person WithNewAge(int newAge) => this with { Age = newAge };
}

// Class
public class Employee
{
    public string Name { get; set; }
    public int Age { get; set; }
    public void Promote() => Console.WriteLine($"{Name} promoted");
}

// Struct
public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
    public Point(int x, int y) => (X, Y) = (x, y);
}

class Program
{
    static void Main()
    {
        // Record: Immutable, value equality
        var p1 = new Person("Alice", 30);
        var p2 = new Person("Alice", 30);
        Console.WriteLine(p1 == p2); // True
        var p3 = p1.WithNewAge(31);
        Console.WriteLine(p3.Age); // 31

        // Class: Mutable, reference equality
        var e1 = new Employee { Name = "Bob", Age = 40 };
        var e2 = new Employee { Name = "Bob", Age = 40 };
        Console.WriteLine(e1 == e2); // False
        e1.Promote(); // Bob promoted

        // Struct: Lightweight, value equality
        var point1 = new Point(1, 2);
        var point2 = new Point(1, 2);
        Console.WriteLine(point1.Equals(point2)); // True
    }
}

C#Thread SafetyShared Resources
Writing thread-safe code in C# ensures correct behavior in multithreaded environments, particularly when accessing shared resources.

Best Practices:
- Minimize Shared State: Prefer immutable objects or local variables to avoid synchronization.
- Use Synchronization Primitives:
- `lock` for simple critical sections.
- `Monitor` for conditional locking or signaling.
- `SemaphoreSlim` for resource pools.
- `Interlocked` for atomic operations (e.g., increment).
- Leverage Concurrent Collections: Use `ConcurrentDictionary`, `ConcurrentQueue`, etc., for thread-safe collections.
- Avoid Deadlocks: Acquire locks in a consistent order and use timeouts (e.g., `Monitor.TryEnter`).
- Use Task-Based Async: Prefer `async/await` over manual threading for I/O-bound work.
- Immutable Data: Use records or `ImmutableList` for thread-safe data sharing.
- Test Thoroughly: Stress-test with multiple threads to detect race conditions.

Handling Shared Resources:
- Protect shared resources with locks or concurrent collections.
- Use `Interlocked` for simple atomic updates.
- Monitor with diagnostics tools like Concurrency Visualizer to detect issues.

Use Cases:
- Shared caches, counters, or queues in high-concurrency APIs.
- Parallel processing of data with shared state.
Example:
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    private static int _counter = 0;
    private static readonly object _lock = new object();
    private static readonly ConcurrentDictionary _cache = new ConcurrentDictionary();

    static async Task Main()
    {
        // Lock for shared resource
        var tasks = new Task[5];
        for (int i = 0; i < 5; i++)
        {
            tasks[i] = Task.Run(() =>
            {
                lock (_lock)
                {
                    _counter++;
                    Console.WriteLine($"Counter: {_counter}");
                }
            });
        }
        await Task.WhenAll(tasks);

        // Concurrent collection
        tasks = new Task[5];
        for (int i = 0; i < 5; i++)
        {
            tasks[i] = Task.Run(() =>
            {
                _cache.TryAdd($"key{i}", i);
                Console.WriteLine($"Added key{i}");
            });
        }
        await Task.WhenAll(tasks);
        Console.WriteLine($"Cache count: {_cache.Count}");

        // Interlocked for atomic operations
        _counter = 0;
        tasks = new Task[5];
        for (int i = 0; i < 5; i++)
        {
            tasks[i] = Task.Run(() => Interlocked.Increment(ref _counter));
        }
        await Task.WhenAll(tasks);
        Console.WriteLine($"Interlocked Counter: {_counter}"); // 5
    }
}

C#CovarianceContravarianceGenerics
Covariance and contravariance enable flexible type conversions in generic interfaces, enhancing polymorphism.

- Covariance: Allows a generic interface to be used with a more derived type than specified (e.g., `IEnumerable` can be treated as `IEnumerable`). Denoted with `out` in C#.
- Contravariance: Allows a generic interface to be used with a less derived type (e.g., `IComparer` can be used as `IComparer`). Denoted with `in`.

Constraints:
- Covariance applies to output positions (return types), requiring `out`.
- Contravariance applies to input positions (parameters), requiring `in`.
- Only interfaces and delegates support variance.

Real-World Use Case:
- Covariance: Using `IEnumerable` as `IEnumerable` in a pet store app to process all animals.
- Contravariance: Using an `IComparer` to compare `Dog` objects, allowing a general comparer for specific types.
Example:
using System;
using System.Collections.Generic;

public class Animal { }
public class Dog : Animal { }

// Covariant interface
public interface IReader { T Read(); }

// Contravariant interface
public interface IWriter { void Write(T item); }

public class AnimalReader : IReader
{
    public Animal Read() => new Animal();
}

public class DogWriter : IWriter
{
    public void Write(Dog item) => Console.WriteLine("Wrote dog");
}

class Program
{
    static void Main()
    {
        // Covariance
        IReader dogReader = new AnimalReader(); // AnimalReader produces Animals, safe for Dogs
        Animal animal = dogReader.Read(); // Safe

        // Contravariance
        IWriter animalWriter = new DogWriter(); // DogWriter can write Dogs, safe for Animals
        animalWriter.Write(new Dog()); // Wrote dog

        // Real-world: IEnumerable (covariant)
        IEnumerable dogs = new List { new Dog() };
        IEnumerable animals = dogs; // Covariant
        Console.WriteLine(animals.Count()); // 1
    }
}