Bird
Raised Fist0
C Sharp (C#)programming~10 mins

Contravariance with in keyword in C Sharp (C#) - Step-by-Step Execution

Choose your learning style10 modes available
Concept Flow - Contravariance with in keyword
Define interface with 'in' keyword
↓
Create base and derived classes
↓
Assign derived type to base type variable
↓
Call method accepting base type
↓
Contravariance allows safe assignment
Contravariance lets you use a less derived type where a more derived type is expected, enabled by the 'in' keyword in generic interfaces.
Execution Sample
C Sharp (C#)
interface IContravariant<in T> { void Set(T item); }
class Animal {}
class Dog : Animal {}
class ContravariantSetter<T> : IContravariant<T> { public void Set(T item) { } }
IContravariant<Animal> animalSetter = new ContravariantSetter<Dog>();
animalSetter.Set(new Animal());
Shows how contravariance allows assigning IContravariant<Dog> to IContravariant<Animal> using 'in' keyword.
Execution Table
StepActionType involvedAssignment Valid?Reason
1Define interface IContravariant<in T>T is contravariantN/AAllows T to be used as input parameter type
2Create classes Animal and Dog : AnimalDog inherits AnimalN/ADog is subtype of Animal
3Create instance of ContravariantSetter<Dog>IContravariant<Dog>N/AConcrete implementation for Dog
4Assign to IContravariant<Animal> variableIContravariant<Dog> to IContravariant<Animal>No'in' keyword allows contravariance but assignment is from less derived to more derived, which is invalid
5Call Set(new Animal()) on variableSet(Animal)Runtime errorDog setter expects Dog, Animal passed (contravariance only safe for input types, but method expects Dog)
6Call Set(new Dog()) on variableSet(Dog)SuccessDog instance matches expected type
💡 Execution stops after method calls; contravariance allows assignment from more derived to less derived generic types, but method parameter types must be compatible at runtime.
Variable Tracker
VariableStartAfter Step 3After Step 4After Step 5
animalSetternullContravariantSetter<Dog> instanceAssignment invalid, no assignment occursMethod called with Animal or Dog instance
Key Moments - 2 Insights
Why can I assign IContravariant<Dog> to IContravariant<Animal> but not vice versa?
Because the 'in' keyword makes T contravariant, allowing assignment from more derived to less derived types (Animal to Dog), but not the other way around.
Why does calling Set(new Animal()) cause a runtime problem?
Because the actual implementation expects Dog, but Animal is passed, which is not safe. Contravariance applies to assignment compatibility, but method calls must respect actual parameter types.
Visual Quiz - 3 Questions
Test your understanding
Look at the execution_table at step 4, what does the 'in' keyword allow?
AAssign unrelated types
BAssign IContravariant<Dog> to IContravariant<Animal>
CAssign IContravariant<Animal> to IContravariant<Dog>
DNo assignment allowed
💡 Hint
Check the 'Assignment Valid?' column at step 4 in execution_table
At which step does calling Set with an Animal instance cause a problem?
AStep 5
BStep 3
CStep 4
DStep 6
💡 Hint
Look at the 'Reason' column for runtime errors in execution_table
If we remove the 'in' keyword, what happens to the assignment in step 4?
AIt causes runtime error
BIt becomes invalid
CIt remains valid
DIt compiles but fails at runtime
💡 Hint
Recall that 'in' enables contravariance; without it assignment is invalid (see step 4)
Concept Snapshot
Contravariance with 'in' keyword:
- Use 'in' on generic type parameter to allow contravariance
- Allows assignment from more derived to less derived generic types
- Safe only for input (method parameter) types
- Enables flexible and type-safe assignments
- Method calls must respect actual parameter types
Full Transcript
This visual execution shows how contravariance works in C# using the 'in' keyword on generic interfaces. First, an interface IContravariant with 'in T' is defined, allowing T to be used only as input. Then, classes Animal and Dog (which inherits Animal) are created. An instance of ContravariantSetter<Dog> is assigned to a variable of type IContravariant<Animal> thanks to contravariance. Calling Set with a Dog instance works fine, but calling Set with an Animal instance causes a runtime problem because the implementation expects Dog. The key is that contravariance allows assignment compatibility but method calls must respect actual parameter types. This helps write flexible and safe code when dealing with inheritance and generics.

Practice

(1/5)
1.

What does the in keyword specify when used in a generic interface declaration like IComparer?

easy
A. It allows the type parameter to be used only as a return type.
B. It marks the type parameter as contravariant, allowing it to accept less derived types.
C. It restricts the type parameter to value types only.
D. It marks the type parameter as covariant, allowing it to return more derived types.

Solution

  1. Step 1: Understand the role of in keyword in generics

    The in keyword declares a generic type parameter as contravariant, meaning it can accept arguments of less derived types safely.
  2. Step 2: Contrast with covariance

    Covariance (using out) allows returning more derived types, while contravariance (using in) allows accepting less derived types as input.
  3. Final Answer:

    It marks the type parameter as contravariant, allowing it to accept less derived types. -> Option B
  4. Quick Check:

    in means contravariant = It marks the type parameter as contravariant, allowing it to accept less derived types. [OK]
Hint: Remember: 'in' means input, contravariant accepts less derived types [OK]
Common Mistakes:
  • Confusing 'in' with covariance (out keyword)
  • Thinking 'in' restricts to value types
  • Assuming 'in' applies to return types
2.

Which of the following is the correct syntax to declare a contravariant generic interface IProcessor with type parameter T?

?
easy
A. interface IProcessor { void Process(T item); }
B. interface IProcessor { void Process(T item); }
C. interface IProcessor<T> { T Process(); }
D. interface IProcessor { T Process(); }

Solution

  1. Step 1: Identify contravariant syntax

    Contravariance uses in keyword before the type parameter in interface declaration.
  2. Step 2: Check method usage

    Contravariant type parameters can only be used as input parameters, not as return types. So method must accept T as parameter, not return it.
  3. Final Answer:

    interface IProcessor<in T> { void Process(T item); } -> Option A
  4. Quick Check:

    Contravariant = in + input parameter only = interface IProcessor<in T> { void Process(T item); } [OK]
Hint: Use 'in' with input parameters only, no returns [OK]
Common Mistakes:
  • Using 'out' instead of 'in' for contravariance
  • Returning type T in contravariant interface
  • Omitting 'in' keyword
3.

Consider the following code snippet:

interface IConsumer { void Consume(T item); }

class Animal { }
class Dog : Animal { }

IConsumer<Animal> animalConsumer = new Consumer();
IConsumer<Dog> dogConsumer = animalConsumer;

What will happen when this code runs?

medium
A. It throws a runtime exception.
B. It causes a compile-time error because types are incompatible.
C. It compiles and runs successfully due to contravariance.
D. It compiles but causes a runtime error.

Solution

  1. Step 1: Understand contravariance assignment

    Contravariance allows assigning IConsumer<Animal> to IConsumer<Dog> because Dog is more derived than Animal.
  2. Step 2: Check code behavior

    The assignment IConsumer<Dog> dogConsumer = animalConsumer; is valid and compiles because of in keyword on T.
  3. Final Answer:

    It compiles and runs successfully due to contravariance. -> Option C
  4. Quick Check:

    Contravariance allows assignment from base to derived = It compiles and runs successfully due to contravariance. [OK]
Hint: Contravariant interface allows base-to-derived assignment [OK]
Common Mistakes:
  • Thinking assignment is invalid due to type mismatch
  • Confusing contravariance with covariance
  • Expecting runtime errors
4.

Given this interface declaration:

interface IHandler { void Handle(T item); }

Which of the following code snippets will cause a compile-time error?

?
medium
A. IHandler<object> handler = new Handler<string>();
B. IHandler<string> handler = new Handler<object>();
C. IHandler<string> handler = new Handler<string>();
D. IHandler<object> handler = new Handler<object>();

Solution

  1. Step 1: Recall contravariance assignment rules

    Contravariance allows assignment from less derived to more derived generic types, e.g., IHandler<Animal> to IHandler<Dog>.
  2. Step 2: Analyze each option

    IHandler<object> handler = new Handler<string>(); tries to assign IHandler<string> to IHandler<object>, which is from more derived to less derived generic types, invalid for contravariance.
  3. Final Answer:

    IHandler<object> handler = new Handler<string>(); -> Option A
  4. Quick Check:

    Contravariance disallows assigning derived generic to base generic = IHandler<object> handler = new Handler<string>(); [OK]
Hint: Contravariant assignment reverses subtype direction [OK]
Common Mistakes:
  • Assigning derived generic to base generic incorrectly
  • Confusing contravariance with covariance
  • Ignoring compile-time type safety
5.

You have these interfaces and classes:

interface IProcessor { void Process(T item); }
class Vehicle { }
class Car : Vehicle { }

class VehicleProcessor : IProcessor<Vehicle> {
    public void Process(Vehicle item) { Console.WriteLine("Processing vehicle"); }
}

Which assignment is valid and why?

?
hard
A. IProcessor<Vehicle> vehicleProcessor = new VehicleProcessor(); // Invalid because of contravariance
B. IProcessor<Car> carProcessor = new IProcessor<Vehicle>(); // Invalid because of covariance
C. IProcessor<Vehicle> vehicleProcessor = new IProcessor<Car>(); // Valid because Car is derived
D. IProcessor<Car> carProcessor = new VehicleProcessor(); // Valid because of contravariance

Solution

  1. Step 1: Understand contravariance assignment direction

    Contravariance allows assigning IProcessor<Vehicle> to IProcessor<Car> because Car is derived from Vehicle.
  2. Step 2: Evaluate each option

    IProcessor<Car> carProcessor = new VehicleProcessor(); // Valid because of contravariance assigns VehicleProcessor (implements IProcessor<Vehicle>) to IProcessor<Car> variable, which is valid due to contravariance.
  3. Final Answer:

    IProcessor<Car> carProcessor = new VehicleProcessor(); // Valid because of contravariance -> Option D
  4. Quick Check:

    Contravariance allows base to derived assignment = IProcessor<Car> carProcessor = new VehicleProcessor(); // Valid because of contravariance [OK]
Hint: Contravariant interface allows base type instance for derived type variable [OK]
Common Mistakes:
  • Confusing covariance and contravariance directions
  • Trying to assign interface instances incorrectly
  • Ignoring inheritance hierarchy in assignments
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard