问题
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?
回答1:
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).
回答2:
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.
来源:https://stackoverflow.com/questions/25647220/how-to-ensure-that-implementations-of-an-interface-have-a-connection-string-if-t