58

Let's say there is a member SomeMethod in an interface ISomeInterface as follows:

public interface ISomeInterface
{
    int SomeMethod(string a);
}

For the purposes of my program, all consumers of ISomeInterface act upon the assumption that the returned int is greater than 5.

Three ways come to mind to solve this -

1) For every object that consumes ISomeInterface, they assert that the returned int > 5.

2) For every object that implements ISomeInterface, they assert that the int they're about to return is > 5.

Both the above two solutions are cumbersome since they require the developer to remember to do this on every single implementation or consumption of ISomeInterface. Furthermore this is relying upon the implementation of the interface which isn't good.

3) The only way I can think to do this practically is to have a wrapper that also implements ISomeInterface, and returns the underlying implementation as follows:

public class SomeWrapper : ISomeInterface
{
    private ISomeInterface obj;

    SomeWrapper(ISomeInterface obj)
    {
        this.obj = obj;
    }

    public int SomeMethod(string a)
    {
        int ret = obj.SomeMethod("hello!");
            if (!(ret > 5))
            throw new Exception("ret <= 5");
        else
            return ret;
    }
}

The problem though now is that we're again relying on an implementation detail of ISomeInterface via what the SomeWrapper class does, although with the benefit that now we've confined it to a single location.

Is this the best way to ensure an interface is implemented in the expected manner, or is there a better alternative? I understand interfaces may not be designed for this, but then what is the best practice to use some object under the assumption that it behaves a certain way more than what I can convey within its member signatures of an interface without needing to do assertions upon every time it's instanced? An interface seems like a good concept, if only I could also specify additional things or restrictions it's supposed to implement.

10
  • Note that even if !(ret > 5), it is possible that ret == 5. Commented Feb 2, 2020 at 23:55
  • 21
    What you are discovering is that first, interfaces are a lousy implementation of the contract concept, and second C# does not have dependent types. Even if your type system problem was solved, you'd still have the first problem. Interfaces make it easy to express contracts like "I have three methods Open, Transmit and Close" but do not make it easy to express "Transmit must be called after Open but before Close..." and so on. Commented Feb 3, 2020 at 18:52
  • 1
    Is this example coming out of the blue or is there a real explanation or use case to it? just curious
    – glace
    Commented Feb 4, 2020 at 6:09
  • 1
    @glace out of the blue as a simple toy example that encapsulates my question, but I had real use cases where I needed to assert more things in an interface than what's provided via the method signature. Thankfully Euphoric's answer works well.
    – user4779
    Commented Feb 4, 2020 at 6:37
  • @EricLippert "Transmit must be called after Open" is easy enough to encode in types/interfaces by having open be the only method of UnopenedFoo, returning a Foo with transmit and close. Using types to prevent calling transmit after close is much harder, e.g. needing linear/affine/uniqueness types. Most languages do this with scope instead, e.g. putting close logic in a destructor, or both open and close logic in a withFoo function/method taking a callback.
    – Warbo
    Commented Feb 5, 2020 at 14:50

8 Answers 8

165

Instead of returning an int, return a value object that has the validation hard-coded. This is a case of primitive obsession and its fix.

// should be class, not struct as struct can be created without calling a constructor
public class ValidNumber
{
    public int Number { get; }

    public ValidNumber(int number)
    {
        if (number <= 5)
            throw new ArgumentOutOfRangeException("Number must be greater than 5.")
        Number = number;
    }
}

public class Implementation : ISomeInterface
{
    public ValidNumber SomeMethod(string a)
    {
        return new ValidNumber(int.Parse(a));
    }
}

This way, the validation error would happen inside the implementation, so it should show up when developer tests this implementation. Having the method return a specific object makes it obvious that there might be more to it than just returning a plain value.

20
  • 9
    This is a good suggestion, but it requires changing the return type. This would be pretty impactful to existing clients of your interface. If you want a similar approach that still allows you to return int, check my answer. Commented Feb 2, 2020 at 7:20
  • 2
    This way ValidNumber is immutable, can only be setted one. If the need of a mutable version is needed, or you really need to use a struct, the validation check can be performed in the setter, instead of the constructor.
    – bracco23
    Commented Feb 2, 2020 at 8:21
  • 4
    @bracco23 Immutability is a valuable property in this case. And the struct still won't work, as it is possible to create an empty struct instance with all set to zero.
    – Euphoric
    Commented Feb 2, 2020 at 8:42
  • 9
    @user4779: This is one solution for some cases, you should definitely not start to use it now each and everywhere, especially not when it requires to wrap all simple types into some helper class. Instead, inform yourself about Design by Contract in C#
    – Doc Brown
    Commented Feb 2, 2020 at 8:54
  • 11
    @DocBrown Design by Contract doesn't seem to be going strong with current versions of C#. While a useful pattern, it is not something you see often, if not at all.
    – Euphoric
    Commented Feb 2, 2020 at 9:04
25

To complement the other answers, I'd like to partially comment on the following note in the OP by providing a broader context:

An interface seems like a good concept, if only I could also specify additional things or restrictions it's supposed to implement.

You are making a good point here! Let us consider on which levels we can specify such restrictions (constraints):

  1. in the language's type system
  2. via meta annotations internal or external to the language and external tools (static analysis tools)
  3. via runtime assertions — as seen in other answers
  4. documentation targetted at humans

I elaborate on every item below. Before that, let me say that the constraints get weaker and weaker the more you transition from 1 to 4. For example, if you only rely on point 4, you are relying on developers correctly applying the documentation, and there is no way anyone is able to tell you whether those constraints are fulfilled other than humans themselves. This, of course, is much more bound to contain bugs by the very nature of humans.

Hence, you always want to start modelling your constraint in point 1, and only if that's (partially) impossible, you should try point 2, and so on. In theory, you always would like to rely on the language's type system. However, for that to be possible you would need to have very powerful type systems, which then become untractable — in terms of speed and effort of type checking and in terms of developers being able to comprehend types. For the latter, see Is the Scala 2.8 collections library a case of “the longest suicide note in history”?.

1. Type System of the Language

In most typed (OO-flavored) languages such as C# it is easily possible to have the following interface:

public interface ISomeInterface
{
  int SomeMethod(string a);
}

Here, the type system allows you to specify types such as int. Then, the type checker component of the compiler guarantees at compile time that implementors always return an integer value from SomeMethod.

Many applications can already be built with the usual type systems found in Java and C#. However, for the constraint you had in mind, namely that the return value is an integer greater than 5, these type systems are too weak. Indeed, some languages do feature more powerful type systems where instead of int you could write {x: int | x > 5}1, i.e. the type of all integers greater than 5. In some of these languages, you also need to prove that as an implementor you always really return something greater than 5. These proofs are then verified by the compiler at compile time as well!

Since C# does not feature some types, you have to resort to points 2 and 3.

2. Meta Annotations internal/external to the Language

This other answer already provided an example of meta annotations inside the language, which is Java here:

@NotNull
@Size(min = 1)
public List<@NotNull Customer> getAllCustomers() {
  return null;
}

Static analysis tools can try to verify whether these constraints specified in the meta annotations are fulfilled or not in the code. If they cannot verify them, these tools report an error.2 Usually, one employs static analysis tools together with the classic compiler at compile time meaning that you get constraint checking at compile time as well here.

Another approach would be to use meta annotations external to the language. For example, you can have a code base in C and then prove the fulfillment of some constraints in a totally different language referring to that C code base. You can find examples under the keywords "verifying C code", "verifying C code Coq" among others.

3. Runtime Assertions

At this level of constraint checking, you outsource the checking from compile and static analysis time completely to runtime. You check at runtime whether the return value fulfills your constraint (e.g. is greater than 5), and if not, you throw an exception.

Other answers already showed you how this looks code-wise.

This level offers great flexibility, however, at the cost of deferring constraint checking from compile time to runtime. This means that bugs might get revealed very late, possibly at the customer of your software.

4. Documentation Targetted At Humans

I said that runtime assertions are quite flexible, however, still they cannot model every constraint you could think of. While it's easy to put constraints on return values, it's for instance hard (read: untractable) to model interaction between code components as that would require some kind of "supervisory" view on code.

For example, a method int f(void) might guarantee that its return value is the current score of the player in the game, but only as long as int g(void) has not been called superseeding the return value of f. This constraint is something you probably need to defer to human-oriented documentation.


1: Keywords are "dependent types", "refinement types", "liquid types". Prime examples are languages for theorem provers, e.g. Gallina which is used in the Coq proof assistant.

2: Depending on what kind of expressiveness you allow in your constraints, fulfillment checking can be an undecidable problem. Practically, this means that your programmed method fulfills the constraints you specified, but the static analysis tool is unable to prove them. Or put differently, there might be false negatives in terms of errors. (But never false positives if the tool is bug-free.)

8
  • 3
    Mind, you can still use the type system of C#, it just isn't as simple as saying 0..5 as in something like Pascal. You can always make your own type that captures an integer and only allows the values you want - not as convenient, but still works well for correctness.
    – Luaan
    Commented Feb 3, 2020 at 8:55
  • 1
    @Luaan You mean creating a new class, say FiveUppedInt, and use for example new FiveUppedInt(0) and new FiveUppedInt(20) to represent 5 and 25, respectively?
    – ComFreek
    Commented Feb 3, 2020 at 8:59
  • 1
    That would probably be a bit too confusing. I mean just making sure you can't create a value of FlobberCount smaller than five. You can't rely on the compiler enforcing anything on the interface where you create FlobberCount, but anyone who gets an instance of FlobberCount knows it must be five or more. You only decide what to do with invalid values on the interface, rather than having that decision spread throughout your codebase (potentially including 3rd parties). The interface itself is tight.
    – Luaan
    Commented Feb 3, 2020 at 9:05
  • 1
    @supercat I largely agree with your comment except for the part: "if humans follow them the automated checks will be largely redundant". Think of the usually listed advantages of typed languages. If humans could always follow docs, they wouldn't need types and Java wouldn't need NPEs. Human-readable docs need to be the primary source for humans, but for computers automatically verifiable constraints need to be the primary source (= main point of my post). And that often start with the type system.
    – ComFreek
    Commented Feb 5, 2020 at 8:04
  • 1
    @ComFreak I was going to suggest your FiveUppedInt, although it should be SixUpped and wrap an unsigned int (i.e. natural number) to prevent FiveUppedInt(-5). I've seen this approach called "correct by construction", since the invariant is guaranteed to be satisfied due to the nature of the representation (no need for separate proofs/justifications; less need for dependent types). Other examples are NonEmptyList<T> pairing a T (first element) with a List<T> (the rest); or a sorted list of numbers where each element is the (non-negative) difference from the previous element.
    – Warbo
    Commented Feb 5, 2020 at 15:13
23

You're trying to design by contract, where that contract is that the return value must be greater than 5. Unfortunately, if you're relying on an interface, the only contract you have is the method signatures.

I'd suggest using an abstract class instead. Here's an approach I would take in Java:

public abstract class SomeAbstraction {
    public final int someMethod(String a) {
        // You may want to throw an exception instead
        return Math.max(6, someAbstractMethod());
    }

    protected abstract int someAbstractMethod();
}

As long as your sub-classes are isolated in a separate package (where someAbstractMethod would be inaccessible) then clients of this abstraction will only have access to someMethod and can safely rely on the return value always being greater than 5. The contract is enforced in one place, and all sub-classes must adhere whether they know it or not. Using the final keyword on someMethod has the added benefit of preventing sub-classes from forcibly breaking that contract by overriding it.

Note: Typically, a contract violation should be exceptional behavior. Thus, you'd probably want to throw an exception or log an error instead of just forcing the return value. But this depends entirely on your use case.

6
  • 37
    If the called method returns 4, something is wrong and needs fixing. If you silently change it to six, something is still wrong and needs fixing, the caller just doesn’t know.
    – gnasher729
    Commented Feb 2, 2020 at 9:31
  • 3
    @gnasher729 Right, someMethod should throw in that case.
    – bdsl
    Commented Feb 2, 2020 at 10:43
  • @bdsl not necessarily. The caller might not need to know eg because it’s the other side of an API and letting an app exception out through that API might be a security risk. So you may just want to log the failure internally and rerun a default value. Exceptions are useful but they aren’t always the answer.
    – David Arno
    Commented Feb 2, 2020 at 14:14
  • 3
    @gnasher729: It's contextual. Silent alterations may be acceptable, e.g. rounding a decimal to a certain precision or altering a string. The wrapper may exist explicitly to quietly enforce some alterations (e.g. a profanity filter). You're right that there are cases where you'd prefer for it to blow up specifically to point out an error, but it's equally possible that you want graceful handling and immediate alteration. Context is key.
    – Flater
    Commented Feb 2, 2020 at 23:20
  • 3
    The issue with this is that your thing is now a class, rather than an interface; and THIS will break things that want to use it. For example; now they can't implement this and inherit something else.
    – UKMonkey
    Commented Feb 3, 2020 at 17:18
1

You may have validating annotations that restrict the aceptable returned values. Here is an example in Java for Spring taken from baeldung.com but in C# you have a similar feature:

   @NotNull
   @Size(min = 1)
   public List<@NotNull Customer> getAllCustomers() {
        return null;
   }

If you use this approach you must consider that:

  • You need a validation framework for your language, in this case C#
  • Adding the annotation is just one part. Having the annotation validated and how this validation errors are handled are the important part. In the case of Spring it creates a proxy using dependency injection that would throw a Runtime ValidationException
  • Some annotations may help your IDE to detect bugs at compile time. For example, if you use @NonNull the compiler can check that null is never returned. Other validations needs to be enforced at runtime
  • Most validation frameworks allow you to create custom validations.
  • It is very useful for processing input data where you may need to report more than one broken validation at the same time.

I do not recommend this approach when the valdiation is part of the business model. In this case the answer from Euphoric is better. The returned object will be a Value Object that will help you create a rich Domain Model. This object should have a meaningful name with the restrictions acording to the type of business you do. For example, here I can validate that dates are reasonables for our users:

public class YearOfBirth

     private final year; 

     public YearOfBirth(int year){
        this.year = year;    
        if(year < 1880){
            throw new IllegalArgumentException("Are you a vampire or what?");
        }  
        if( year > 2020){
            throw new IllegalArgumentException("No time travelers allowed");     
         }
     }
}

The good part is that this kind of object can attract very small methods with simple and testeable logic. For example:

public String getGeneration(){
      if( year < 1964){
           return "Baby Boomers";
      }
      if( year < 1980 ){
           return "Generation X";
      }
      if( year < 1996){
           return "Millenials";
      }
      // Etc...
}
3
  • 3
    While this would suffice for Java, this question is tagged C#. Commented Feb 2, 2020 at 22:45
  • 1
    @GregBurghardt Just added a link to how to do it in C#. My understanding is that softwareengineering is more about the development practices than the code implementation. This way this question may be code-agnostic
    – Borjab
    Commented Feb 3, 2020 at 9:05
  • Definitely don't hardcode 2020. And this program will not be helpful for genealogists. Commented Feb 3, 2020 at 10:46
1

It doesn't have to be complicated:

public interface ISomeInterface
{
    // Returns the amount by which the desired value *exceeds* 5.
    uint SomeMethodLess5(string a);
}
1

You can write a unit test to find all implementations of an interface, and run a test against each of them.

var iType= typeof(ISomeInterface);
var types = AppDomain.CurrentDomain.GetAssemblies()
    .SelectMany(s => s.GetTypes())
    .Where(p => iType.IsAssignableFrom(p));

foreach(var t in types)
{
    var result = t.SomeMethod("four");
    Assert.IsTrue(result > 4, "SomeMethod implementation result was less than 5");
}

When a new implementation of ISomeInterface is added to your project, this test should be able to test it and fail if it returns something less than 5. This, of course, assumes you can test it properly from the input to this method alone, which I am using "four" here. You might need to do other ways to setup this call.

2
  • Problem I can imagine with this is that a valid input isn't defined. How will you know that "four" is an acceptable input for that interface implementation? Commented Feb 5, 2020 at 12:33
  • @Tschallacka I don't. like I said, "You might need to do other ways to setup this call"
    – Neil N
    Commented Feb 5, 2020 at 13:41
0

Intellisense Code Documentation

Most languages have a form of Intellisense Code Documentation. For C#, you can find information on it here. In a nutshell, it is comment documentation the you IDE Intellisense can parse, and make available to the user when they want to use it.

What your interface documentation says is the behavior of a call is the only real contract of how it should behave. After all, your interface doesn't know how to tell the difference between a random number generator that gives good output, and one that always returns 4 (determined by a perfectly random die roll).

Unit Tests to verify the documentation is implemented faithfully

After the documentation, you should have a unit test suite for your interface that given an instance of a class that implements the interface, gives off the expected behavior when run through various use cases. While you could bake the unit tests into an abstract class to reinforce the behavior, that is overkill and will probably cause more pain than it's worth.

Meta Annotations

I fill I should also mention some languages also support some form of meta annotations. Effectively assertions that are evaluated at compile time. While they are limited in the types of checks they can do, they can at least verify simple programing mistakes at compile time. This should be considered more a compiler assist with the Code Documentation than an enforcer of the interface.

-1

In Java if you have a custom constructor, the empty constructor doesn't apply anymore and so you can force calling the custom constructor. My understanding is that C# works the same.

So you could have something like

public class SecondClass {
     public SecondClass(//some argument){
     // do your checks here and throw an exception
     }
}

public abstract class FirstClass extends SecondClass{
    public FirstClass(){
     // we're forced to call super() here because no empty constructor in SecondClass
     super(//some argument)
      }
}

This is pretty rough, but you get the idea.

Not the answer you're looking for? Browse other questions tagged or ask your own question.