Interfaces cannot contain fields, but they can contain properties. In most cases, properties can be used like fields, and there is no difficulty with saying:
interface ISomeProperties
{int prop1 {get;set;}; string prop2 {get; set;}}
interface IMoreProperties
{string prop3 {get;set;}; double prop4 {get; set;}}
interface ICombinedProperties : ISomeProperties, IMoreProperties;
{ }
Given a storage location of type ICombinedProperties
, one may access all four properties directly and without fuss.
It should be noted, though, that there are a few things that can be done with fields which cannot be done with properties. For example, while a field may be passed to Interlocked.Increment
a property cannot; attempting to Interlocked.Increment
a property by copying it to a variable, calling Interlocked.Increment
on that, and then copying the result back to the property might "work" in some cases, but would fail if two threads attempted to do the same thing simultaneously (it would be possible e.g. for both threads to read a value of 5, increment it to 6, and then write back 6, whereas having two threads call Interlocked.Increment
on a field that was initially equal to 5 would be guaranteed to yield 7.).
To get around this, it may be necessary to have the interface include some methods which either perform an interlocked method on a field (e.g. one could have a function which calls Interlocked.Increment
on the field and returns the result) and/or include functions which will call a specified delegate with a field as a ref
parameter (e.g.
delegate void ActionByRef<T1>(ref T1 p1);
delegate void ActionByRef<T1,T2>(ref T1 p1, ref T2 p2);
delegate void ActionByRef<T1,T2,T3>(ref T1 p1, ref T2 p2, ref T3 p3);
interface IThing
{ // Must allow client code to work directly with a field of type T.
void ActOnThing(ActionByRef<T> proc);
void ActOnThing<ExtraT1>(ActionByRef<T, ExtraT1> proc, ref ExtraT1 ExtraP1);
void ActOnThing<ExtraT1, ExtraT2>
(ActionByRef<T> proc, ref ExtraT1 ExtraP1, ref ExtraT2 ExtraP2);
}
Given an instance of the interface, one could do something like:
theInstance.ActOnThing(
(ref int param) => Threading.Interlocked.Increment(ref param)
);
or, if one had local variables maskValue
and xorValue
and wanted to atomically update the field with field = (field & maskValue) ^ xorValue
:
theInstance.ActOnThing(
(ref int Param, ref int MaskValue, ref int XorValue) => {
int oldValue,newValue;
do {oldValue = param; newValue = (oldValue & MaskValue) ^ XorValue;
while (Threading.Interlocked.CompareExchange(ref Param, newValue, oldValue) !=
oldValue),
ref maskValue, ref xorValue);
);
If there were only a few types of actions one would want to perform on the fields, it would be simplest to simply include them within the interface. On the other hand, the approach given above allows an interface to expose its fields in such a way as to allow clients to perform arbitrary sequences of actions upon them.