C# Interface Property: Syntax and Implementation
c# interface property: Learn how to declare and implement properties in C# interfaces, including implicit and explicit implementation, get-only and init-only accessors...
c# interface property requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
When you declare a property in a C# interface, you are defining a contract that any implementing type must satisfy. The syntax is straightforward, but several details about implementation and accessibility often trip up developers. This article covers the core rules for declaring interface properties, how to implement them in classes and structs, and the less obvious behaviors that appear when you mix explicit implementation, accessor modifiers, and default interface members.
Declaring a Property in an Interface
An interface property declaration looks like a class property without a body. You specify the type and the accessors that the implementing type must provide. For example:
public interface IProduct { string Name { get; set; } decimal Price { get; } }
Here, Name requires both a getter and a setter, while Price only requires a getter. The interface does not contain any implementation—no backing field, no logic. It simply states that any type claiming to be an IProduct must expose these members with compatible accessors.
You can also declare a property with an init accessor instead of set if you want the property to be settable only during object initialization:
public interface IConfig { int Timeout { get; init; } }
This is a compile-time contract. The implementing type must provide an init accessor, and the property cannot be changed after construction unless the implementing type adds extra behavior.
Implementing an Interface Property in a Class
The most common implementation is implicit: the class declares a property with the same name and compatible accessors. The compiler maps the interface member to the class member automatically.
public class Product : IProduct { public string Name { get; set; } public decimal Price { get; private set; } public Product(string name, decimal price) { Name = name; Price = price; } }
Notice that Price is implemented with a private set. The interface only requires a getter, so the implementing class is free to add a setter with any accessibility. The getter must be public because the interface member is public by default. If you try to make the getter less accessible, the compiler will reject it.
For an init-only interface property, the implementation must also use init:
public class Config : IConfig { public int Timeout { get; init; } }
If you need to perform validation or compute the value, you can use an expression-bodied property or a full property with a backing field:
public class Product : IProduct { private decimal _price; public string Name { get; set; } public decimal Price { get => _price; private set => _price = value > 0 ? value : throw new ArgumentOutOfRangeException(nameof(value)); } }
This keeps validation logic in one place and prevents the same checks from being duplicated across request handlers.
Explicit Interface Implementation for Properties
Sometimes you want the property to be accessible only through the interface reference, not through the concrete class. This is called explicit interface implementation. You write the interface name before the property name and omit the access modifier.
public interface IRepository { string ConnectionString { get; } } public class SqlRepository : IRepository { string IRepository.ConnectionString => _connectionString; private readonly string _connectionString; public SqlRepository(string connectionString) { _connectionString = connectionString; } }
Now ConnectionString is only visible when you treat the instance as an IRepository:
var repo = new SqlRepository("Server=..."); // repo.ConnectionString; // compile error IRepository repoInterface = repo; var cs = repoInterface.ConnectionString; // works
Explicit implementation is useful when you have two interfaces that declare the same property name but with different meanings. You can implement both explicitly and keep the class's own API clean.
| Implementation Style | Accessibility | Use Case |
|---|---|---|
| Implicit | Public on the class | Normal contract fulfillment |
| Explicit | Only through the interface | Name conflicts or hiding implementation details |
Explicitly implemented properties are not virtual by default. If you want to allow derived classes to override them, you must add virtual to the explicit implementation, which is rarely done. In most cases, explicit implementation is used to hide the member from the class's public surface.
Get-Only and Init-Only Properties in Interfaces
A get-only interface property is common for read-only data. The implementing class can expose a public getter and a private setter, as shown earlier. The interface does not care how the value is set internally.
With C# 9 and later, you can declare init accessors in interfaces. This is useful for immutable objects that are built through object initializers. The implementing class must provide an init accessor, and the property cannot be changed after initialization.
public interface IAudit { DateTime CreatedAt { get; init; } } public class AuditRecord : IAudit { public DateTime CreatedAt { get; init; } }
You can also combine get-only and init-only in the same interface, but you cannot have both set and init in the same property declaration. The choice depends on whether the value should be mutable after construction.
Default Interface Members and Property Implementation
C# 8 introduced default interface members, allowing interfaces to provide a default implementation for a property. This is not a backing field; it is a computed property that uses other members.
public interface ILogger { string LogPrefix { get; } string FullLogName => $"{LogPrefix}: {DateTime.Now}"; }
A class that implements ILogger must provide LogPrefix but can choose to override FullLogName or use the default. If the class does not implement FullLogName, the default is used when accessed through the interface reference. If accessed through the class type, the default is not available unless the class inherits it explicitly.
Default implementations are useful for adding optional members to an interface without breaking existing implementers. However, they do not create state; they cannot declare a backing field. If you need a property with a backing field, you must implement it in the class.
Common Pitfalls and Runtime Behavior
One common mistake is assuming that an interface property is virtual. It is not. The implementing class can mark the property as virtual to allow further overriding, but the interface itself does not enforce that. If you call the property through an interface reference, the runtime dispatches to the implementing class's method, which may or may not be overridden in a derived class.
Another pitfall is mismatched accessor accessibility. The interface property is public by default. The implementing property's getter must be public. A setter can be more restrictive, but it cannot be less restrictive than the interface. For example, if the interface declares int Value { get; }, you cannot implement it with a private getter.
Explicitly implemented properties cannot be used with object initializers. If you try to set an explicitly implemented property in an object initializer, the compiler will not find it because it is not part of the class's public API.
From a runtime perspective, property accessors are methods. An interface property adds a method call indirection, but the JIT compiler typically inlines simple getters and setters. The performance impact is negligible in most applications. The real cost comes from boxing if you use a value type through an interface reference, but that is a general interface concern, not specific to properties.
Choosing Between Interface Properties and Methods
A property represents a value that is logically part of the object's state. A method represents an action or a computation that may have side effects. When designing an interface, ask whether the member is a simple value access or an operation.
Use a property when:
- The member exposes a value that is cheap to retrieve and does not change unexpectedly.
- The implementing class can store the value in a field or compute it on the fly without significant work.
- You want to allow object initializer syntax for setting the value.
Use a method when:
- The operation is expensive or may throw exceptions that the caller should handle.
- The operation changes the object's state.
- The result is not a stable value (e.g., a timestamp or a random number).
For example, an interface for a repository might have a ConnectionString property and a SaveChanges() method. The property gives access to a configuration value; the method performs an action. Mixing them in the same interface is normal, but the distinction helps keep the contract clear.
A property with a getter and a setter implies that the value can be read and written. If the setter has complex validation or triggers side effects, consider a method like SetName(string name) instead. The interface contract should reflect the intended usage, not just the implementation convenience.
When you need to expose a computed value that depends on other state, a get-only property is often better than a method because it signals that the value is derived and stable within a single read. However, if the computation is expensive, a method like GetTotal() makes that cost explicit to the caller.