Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to enforce the use of a method's return value in C#?

I have a piece of software written with fluent syntax. The method chain has a definitive "ending", before which nothing useful is actually done in the code (think NBuilder, or Linq-to-SQL's query generation not actually hitting the database until we iterate over our objects with, say, ToList()).

The problem I am having is there is confusion among other developers about proper usage of the code. They are neglecting to call the "ending" method (thus never actually "doing anything")!

I am interested in enforcing the usage of the return value of some of my methods so that we can never "end the chain" without calling that "Finalize()" or "Save()" method that actually does the work.

Consider the following code:

//The "factory" class the user will be dealing with
public class FluentClass
{
    //The entry point for this software
    public IntermediateClass<T> Init<T>()
    {
        return new IntermediateClass<T>();
    }
}

//The class that actually does the work
public class IntermediateClass<T>
{
    private List<T> _values;

    //The user cannot call this constructor
    internal IntermediateClass<T>()
    {
        _values = new List<T>();
    }

    //Once generated, they can call "setup" methods such as this
    public IntermediateClass<T> With(T value)
    {
        var instance = new IntermediateClass<T>() { _values = _values };
        instance._values.Add(value);
        return instance;
    }

    //Picture "lazy loading" - you have to call this method to
    //actually do anything worthwhile
    public void Save()
    {
        var itemCount = _values.Count();
        . . . //save to database, write a log, do some real work
    }
}

As you can see, proper usage of this code would be something like:

new FluentClass().Init<int>().With(-1).With(300).With(42).Save();

The problem is that people are using it this way (thinking it achieves the same as the above):

new FluentClass().Init<int>().With(-1).With(300).With(42);

So pervasive is this problem that, with entirely good intentions, another developer once actually changed the name of the "Init" method to indicate that THAT method was doing the "real work" of the software.

Logic errors like these are very difficult to spot, and, of course, it compiles, because it is perfectly acceptable to call a method with a return value and just "pretend" it returns void. Visual Studio doesn't care if you do this; your software will still compile and run (although in some cases I believe it throws a warning). This is a great feature to have, of course. Imagine a simple "InsertToDatabase" method that returns the ID of the new row as an integer - it is easy to see that there are some cases where we need that ID, and some cases where we could do without it.

In the case of this piece of software, there is definitively never any reason to eschew that "Save" function at the end of the method chain. It is a very specialized utility, and the only gain comes from the final step.

I want somebody's software to fail at the compiler level if they call "With()" and not "Save()".

It seems like an impossible task by traditional means - but that's why I come to you guys. Is there an Attribute I can use to prevent a method from being "cast to void" or some such?

Note: The alternate way of achieving this goal that has already been suggested to me is writing a suite of unit tests to enforce this rule, and using something like http://www.testdriven.net to bind them to the compiler. This is an acceptable solution, but I am hoping for something more elegant.

like image 646
Bokonon Avatar asked Mar 23 '11 20:03

Bokonon


People also ask

Why do we use return 0 in C programming?

return 0 in the main function means that the program executed successfully. return 1 in the main function means that the program does not execute successfully and there is some error. return 0 means that the user-defined function is returning false.

Is it compulsory to return a value from a method *?

A JavaScript function can have an optional return statement i.e. it's optional to return a value. This statement should be the last statement in a function.

How do you use a return statement?

A return statement ends the execution of a function, and returns control to the calling function. Execution resumes in the calling function at the point immediately following the call. A return statement can return a value to the calling function.

What is a return value How can it be used?

A return is a value that a function returns to the calling script or function when it completes its task. A return value can be any one of the four variable types: handle, integer, object, or string. The type of value your function returns depends largely on the task it performs.


3 Answers

I don't know of a way to enforce this at a compiler level. It's often requested for objects which implement IDisposable as well, but isn't really enforceable.

One potential option which can help, however, is to set up your class, in DEBUG only, to have a finalizer that logs/throws/etc. if Save() was never called. This can help you discover these runtime problems while debugging instead of relying on searching the code, etc.

However, make sure that, in release mode, this is not used, as it will incur a performance overhead since the addition of an unnecessary finalizer is very bad on GC performance.

like image 190
Reed Copsey Avatar answered Oct 13 '22 05:10

Reed Copsey


You could require specific methods to use a callback like so:

new FluentClass().Init<int>(x =>
{
    x.Save(y =>
    {
         y.With(-1),
         y.With(300)
    });
});

The with method returns some specific object, and the only way to get that object is by calling x.Save(), which itself has a callback that lets you set up your indeterminate number of with statements. So the init takes something like this:

public T Init<T>(Func<MyInitInputType, MySaveResultType> initSetup) 
like image 39
Tejs Avatar answered Oct 13 '22 05:10

Tejs


I can think of three a few solutions, not ideal.

  1. AIUI what you want is a function which is called when the temporary variable goes out of scope (as in, when it becomes available for garbage collection, but will probably not be garbage collected for some time yet). (See: The difference between a destructor and a finalizer?) This hypothetical function would say "if you've constructed a query in this object but not called save, produce an error". C++/CLI calls this RAII, and in C++/CLI there is a concept of a "destructor" when the object isn't used any more, and a "finaliser" which is called when it's finally garbage collected. Very confusingly, C# has only a so-called destructor, but this is only called by the garbage collector (it would be valid for the framework to call it earlier, as if it were partially cleaning the object immediately, but AFAIK it doesn't do anything like that). So what you would like is a C++/CLI destructor. Unfortunately, AIUI this maps onto the concept of IDisposable, which exposes a dispose() method which can be called when a C++/CLI destructor would be called, or when the C# destructor is called -- but AIUI you still have to call "dispose" manually, which defeats the point?

  2. Refactor the interface slightly to convey the concept more accurately. Call the init function something like "prepareQuery" or "AAA" or "initRememberToCallSaveOrThisWontDoAnything". (The last is an exaggeration, but it might be necessary to make the point).

  3. This is more of a social problem than a technical problem. The interface should make it easy to do the right thing, but programmers do have to know how to use code! Get all the programmers together. Explain simply once-and-for-all this simple fact. If necessary have them all sign a piece of paper saying they understand, and if they wilfully continue to write code which doesn't do anythign they're worse than useless to the company and will be fired.

  4. Fiddle with the way the operators are chained, eg. have each of the intermediateClass functions assemble an aggregate intermediateclass object containing all of the parameters (you mostly do it this was already (?)) but require an init-like function of the original class to take that as an argument, rather than have them chained after it, and then you can have save and the other functions return two different class types (with essentially the same contents), and have init only accept a class of the correct type.

The fact that it's still a problem suggests that either your coworkers need a helpful reminder, or they're rather sub-par, or the interface wasn't very clear (perhaps its perfectly good, but the author didn't realise it wouldn't be clear if you only used it in passing rather than getting to know it), or you yourself have misunderstood the situation. A technical solution would be good, but you should probably think about why the problem occurred and how to communicate more clearly, probably asking someone senior's input.

like image 1
Jack V. Avatar answered Oct 13 '22 04:10

Jack V.