Object-Oriented C# · Lesson 5

Interfaces

The loop uses IBill and does not name the concrete class.

An interface names the capability a caller needs

IBill declares that an implementing value exposes an integer Amount getter. It does not prescribe a Paper field layout, a constructor, or the complete behavior of every possible bill type. The loop can therefore depend on that narrow capability while concrete objects supply their own implementation. This is a contract between callers and implementations, not a command to instantiate an interface directly or a requirement that every implementation inherit from Paper.

Paper implements IBill by providing the required public Amount property. Its constructor receives the amount and initializes that property. The sample uses only Paper objects, but the array's declared element type is IBill, so the consuming code is already expressed against the shared contract. A future digital implementation can participate if it satisfies the same expected semantics. Method signatures are only part of that agreement: callers also need a consistent meaning for Amount and any rules about valid values.

Sum two objects through their common view

The array contains a Paper with amount ten and another Paper with amount two. The total begins at zero. During the first foreach visit, bill is observed as an IBill and its getter supplies ten, moving the total to ten. The second supplies two, moving the total to twelve. WriteLine then prints 12. The loop does not access Paper-specific constructor parameters or test which concrete type each element has.

The getter-only interface prevents assignment to Amount through an IBill reference, but it does not prove that every implementation is immutable internally. An implementation could compute its getter from other changing state or expose separate mutating methods. Paper itself uses a get-only auto-property initialized during construction, so this sample's observed amounts stay fixed. Keep the guarantee of the interface separate from the additional behavior of one implementation. Likewise, an IBill array is still a mutable array container even when its element contract offers only a getter.

interface IBill
{
    int Amount { get; }
}

A matching shape still needs shared semantics

Paper's constructor does not reject a negative amount. The interface does not add that validation automatically. If one implementation reports a net total while another reports a discount magnitude, blindly summing both may violate the domain even though every type compiles. Define the meaning and unit of Amount, and decide whether negative values are valid. Avoid expanding IBill with unrelated operations merely because Paper needs a helper; callers should depend on the capability they actually use.

Add another bill, then add another implementation

First append another Paper with amount five. Predict seventeen without changing the loop. Hint: the total uses only the interface getter, so another compatible element requires no new branch. Change the array's order and verify that this simple integer sum remains the same. Then supply a negative Paper in a separate experiment and explain the actual result before deciding whether the application's contract should permit that input.

Next declare a Digital class implementing IBill with a prepared get-only amount, and include one Digital with amount seven alongside the original two Papers. Predict nineteen. Hint: satisfy the same Amount member and keep its unit consistent; do not make the loop switch on type names. Finally sketch a validation boundary for all bill implementations and explain why the compiler's interface check alone cannot enforce a positive-amount business rule across independently written types.

Separate the consumer contract from one implementation

Explain what the loop knows through IBill and what it does not know about Paper. Trace the two visits and their total, then identify which immutability or validity assumptions come from Paper rather than from the getter-only interface. A useful substitution test adds another implementation without changing the consumer. Review whether the new type preserves the meaning of Amount, not merely whether the same member name makes the project compile.

  • The array is typed as IBill, not Paper.
  • The original amounts ten and two produce 12.
  • Another implementing class can participate without a type switch.
  • A getter-only interface does not prove all reachable state is immutable.

A working example

IBill[] bills = { new Paper(10), new Paper(2) };
var total = 0;
foreach (var bill in bills)
{
    total = total + bill.Amount;
}
Console.WriteLine(total);

interface IBill
{
    int Amount { get; }
}

class Paper : IBill
{
    public Paper(int amount) { Amount = amount; }
    public int Amount { get; }
}

Try this
Read the interface member. Do not call a member that exists only on Paper.

Further reading