Imagine you've an interface like this:
public interface IPersonManager
{
public void AddPerson(string name);
}
...and an implementation which we'll going to call DefaultPersonManager
. Let's say we want to be sure that any implementation of IPersonManager
won't be able to give a null or empty string as argument of AddPerson(string name)
. For that matter, we're going to implement a contract class as follows:
[ContractClassFor(typeof(IPersonManager))]
public abstract class IPersonManagerContract : IPersonManager
{
public void AddPerson(string name)
{
Contract.Requires(!string.IsNullOrEmpty(name), "Person's name cannot be a null or empty string");
}
}
...and we'll decorate our IPersonManager
interface with the ContractClassAttribute
attribute:
[ContractClass(typeof(IPersonManagerContractClass))]
public interface IPersonManager
{
public void AddPerson(string name);
}
We talked about a DefaultPersonManager
. It would look like this class:
public class DefaultPersonManager
{
private readonly List<string> _personNames = new List<string>();
public void AddPerson(string name)
{
// "name" argument will be verified by contract class!
_personNames.Add(name);
}
}
Alright!
Now we need to implement a new IPersonManager
implementation which differs from the DefaultPersonManager
in that AddPerson
should persist person names to a SQL database (i.e. SQL Server, it's just an example...). We'll call this implementation DbBackedPersonManager
.
Since DbBackedPersonManager
requires a connection string, we could add a pre-condition in the AddPerson
method implementation of DbBackedPersonManager
:
public void AddPerson(string name)
{
Contract.Requires(ConfigurationManager.ConnectionStrings["SomeConnectionStringId"] != null, "A connection string is required in your application/web configuration file");
}
Wrong: code contracts compiler will say that AddPerson
implements an interface member thus we can't add a Requires (Read this Q&A I found that was answered by Jon Skeet and it's someway related to this topic a long time ago.).
How would be able to ensure that a specific implementation mandatorily requires a connection string to work nicely?
Add the connection string requirement to the constructor of your concrete implementation, i.e.
public class DbBackedPersonManager : IPersonManager
{
private readonly string _connectionString;
public DbBackPersonManager()
{
Contract.Requires(ConfigurationManager.ConnectionStrings["SomeConnectionStringId"] != null, "A connection string is required in your application/web configuration file");
_connectionString = ConfigurationManager.ConnectionStrings["SomeConnectionStringId"];
}
[ContractInvariantMethod]
private void ObjectInvariant()
{
Contract.Invariant(_connectionString != null);
}
// Interface implementation snipped...
}
Then you are only able to instantiate and subsequently use an instance of DbBackedPersonManager
if the connection string exists.
Personally I would just take a string connectionString
parameter and let the instantiator provide the value (they are going to have to read ConfigurationManager.ConnectionStrings
anyway).
Possibly an approach would be creating an unrelated interface called IWithSqlDbBackend
(or any identifier of your preference...) like this:
public interface IWithSqlDbBackend
{
string ConnectionString { get; }
string ConnectionStringId { get; set; }
}
Later, we'll need to create a contract class like this:
[ContractClassFor(typeof(IWithSqlDbBackend))]
public abstract class IWithSqlDbBackendContract : IWithSqlDbBackend
{
public string ConnectionString
{
get
{
Contract.Requires(!string.IsNullOrEmpty(ConnectionStringId), "Connection string id cannot be null or empty");
Contract.Requires(ConfigurationManager.ConnectionStrings[ConnectionStringId] != null, "Connection string must be configured");
Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()), "A connection string cannot be null");
return null;
}
}
public string ConnectionStringId
{
get
{
Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()), "A connection string identifier cannot be null or empty");
return null;
}
}
}
...also we'll need to decorate IWithSqlDbBackend
interface with the so-called ContractClassAttribute
:
[ContractClass(typeof(IWithSqlDbBackendContract))]
public interface IWithSqlDbBackend
{
...
}
...and implement the interface in DbBackedPersonManager
. I'll add here the implementation signature:
public class DbBackedPersonManager : IPersonManager, IWithSqlDbBackend
Finally, if we create an instance of DbBackedPersonManager
and we try to call AddPerson
method implementation but no connection string was previously configured in the application/web configuration file (i.e. web.config or app.config...), our pre-conditions will ensure that our application, service or library isn't satisfying the contract to work with persons stored in a database backend!
Side note
This is just a sample of how a lot of other domains would be able to ensure a bunch of conditions that, due to code contracts contract classes limitations, would be impossible to verify using regular polymorphism and code contracts.