4

I have a class that contains several methods eg.:

class mySqlTool{

    private string _values, _table, _condition, _result;

    public mySqlTool Select(string values = null){
        //this is REQUIRED
        _values = string.Format("select {0} ", values);
        return this;
    }

    public mySqlTool Update(string table){
        //this is REQUIRED
        _table = table;
        return this;
    }

    public mySqlTool Set(string name, String value){
        //this is REQUIRED
        //handle name and value
        return this;
    }

    public mySqlTool From(string table = null){
        //this is REQUIRED
        _table = table;
        return this;
    }
    public mySqlTool Where(string condition = null){
        //this is OPTIONAL
        _condition = condition;
        return this;
    }
    public string Execute(){
        //this is REQUIRED
        //this is samplecode, of course here is checked if its select or update
        //but to keep it short i erased it

        statement = string.Format("{0} {1}", _values, _table);

        if (!string.IsNullOrEmpty(_condition))
        {
            statement += string.Format(" where {0}", _condition);
        }
        //do some with statemen and fill result
        return _result;
    }
}

now I use this in this chaining way:

MySqlTool t = new MySqlTool();
string result = t.Select("a,b,c").From("x").Where("foo=bar").Execute();

My VS provides me with available methods when I hit DOT (.).

My problem is, I want to denie to use some methods before other methods have been used eg:

MySqlTool.Where().Select().From().Execute();

In this case .C() should not be callable befor .A() was called. So to clarify whats allowed and whats not, here a small list

//Allowed
t.Select().From().Execute();
t.Select().From().Where().Execute();
t.Update().Set().Set().Set().Where().Where().Where().Execute();

//not Allowed
t.Select().Where().Execute();
t.Select().Select().Select().From().Execute();
t.From()...
t.Where()...
t.Execute()....

I read some about interfaces and also about state but I'm not sure if this is what im searching for.

So my question:

Is this what I want even possible?

If yes, how is this technique called?

Dwza
  • 6,494
  • 6
  • 41
  • 73

1 Answers1

11

General description - see end for specific comments

Is this what I want even possible?

Not within the same class, no. How would the compiler know what you'd already called? (Imagine you had a method with a parameter of type Test - what methods should be available to call on that?) The type system decides what's valid and what's not - so if there are different sets of valid operations, that suggests different types.

What you can do is have different types representing the different states, which will only include the appropriate methods for state transitions. So you could have something like this:

class Test0 // Initial state
{
    public Test1 A() { ... }
}

class Test1 // After calling A
{
    public Test2 B() { ... }
}

class Test2 // After calling B
{
    // This returns the same type, so you can call B multiple times
    public Test2 B() { ... }

    // This returns the same type, so you can call C multiple times
    public Test2 C() { ... }

    public string DoSomething() { ... }
}

Then you can use:

Test0 t = new Test0();
string x1 = t.A().B().DoSome();
string x2 = t.A().B().C().DoSome();
string x3 = t.A().B().B().B().C().C().C().DoSome();

... but your invalid cases wouldn't compile.

It works, but it's pretty ugly. Without knowing what the methods are meant to achieve, it's hard to suggest anything else - but in many cases a single method with optional parameters may be better, or possibly a builder pattern.

An alternative is to use a single class and validate the calls at execution time instead, of at compile time. That's less helpful when coding, but avoids having a huge mess of types.

Yet another alternative would be to have a single class - and create a single instance - but use interfaces to represent the state. Your class would implement all the interfaces, so it could still just return this:

interface IStart
{
    IMiddle A();
}

interface IMiddle
{
    IFinal B();
}

interface IFinal
{
    IFinal B();
    IFinal C();
    string DoSomething();
}

class Test : IStart, IMiddle, IFinal
{
    public IMiddle A(string x = null) { return this; }
    public IFinal B(string x = null) { return this; }
    public IFinal C(string x = null) { return this; }
    public string DoSomethign { ... }
}

Then you'd have:

IStart t = new Test();
string x1 = t.A().B().DoSome();
string x2 = t.A().B().C().DoSome();
string x3 = t.A().B().B().B().C().C().C().DoSome();

But this feels pretty wrong to me. I'd expect the A, B and C methods to be effectively changing state in some way - so having separate types would indicate which state is available. In the first example, a Test0 definitely doesn't have the state provided by the A call, but a Test1 does... and a Test2 instance has state provided by A and B, and possibly C.

Specific example

For the specific example given, I'd probably just make the constructor handle the required information (the table name) and use properties/indexers for the rest. I'd probably separate out a query command from updates:

SqlQuery query = new SqlQuery("table")
{
    Columns = { "a", "b", "c" },
    Where = { "foo=bar" } // Not sure how you're parameterizing these
};

And:

SqlUpdate update = new SqlUpdate("table")
{
    // Which columns to update with which values
    ["a"] = 10,
    ["b"] = 20,
    Where = { "foo=bar" } // Not sure how you're parameterizing these
};

In each case there'd be an Execute method returning the appropriate results.

Dwza
  • 6,494
  • 6
  • 41
  • 73
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • is this still chainable ? – Dwza Dec 29 '16 at 11:44
  • @Dwza: Did you try it? Why would it not be chainable? – Jon Skeet Dec 29 '16 at 11:45
  • I just read it and asked... using it this way will take some time to redo my code ^^ I just provided a sample. My source I way larger. I will build a sample programm to test this. Ill be back :D – Dwza Dec 29 '16 at 11:47
  • @Dwza: Please be considerate of the time of other people. When there's something you clearly *can* easily check for yourself, it's worth doing so. You're asking people to help you: the more you can show that you're willing to do everything you can, the more willing they're likely to be to help with what you *can't* do. – Jon Skeet Dec 29 '16 at 11:48
  • ill get errors on converting Test1 to Test0. And If I would post my whole source or the relevent parts there is to much explanation needed so this would end up in a closed post with no answers. And I'm saying that because I had this szenario more than once. – Dwza Dec 29 '16 at 11:50
  • @Dwza: Why would you try to convert `Test1` to `Test0`? (You'll need to fill in the code in the braces appropriately, of course - we have on idea what information is within the instance or how the methods affect that.) – Jon Skeet Dec 29 '16 at 11:51
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/131795/discussion-between-dwza-and-jon-skeet). – Dwza Dec 29 '16 at 11:52
  • Note that I didn't ask for your complete code - that's not required to give more context, more idea of what the `A`, `B` and `C` methods do. – Jon Skeet Dec 29 '16 at 11:58
  • Please put them in the *question* instead. – Jon Skeet Dec 29 '16 at 12:16
  • Better now ? :D – Dwza Dec 29 '16 at 12:36
  • @Dwza: Yes, and will add an example. – Jon Skeet Dec 29 '16 at 13:04
  • @JonSkeet, I am coming late to the discussion, I was thinking the OP could solve his problem as I did this way: [here](https://repl.it/EyUM/0). While in the C++ world (my background), a variant of that is an acceptable pattern to do this kind of thing, Rather than posting an answer, I prefer an expert's opinion in doing that in C#. Whats wrong with doing that in C#?. I have also posted in your [discussion between dwza and jon skeet](http://chat.stackoverflow.com/rooms/131795/discussion-between-dwza-and-jon-skeet) – WhiZTiM Dec 30 '16 at 01:59
  • @WhiZTiM: That's basically equivalent to the first part of my answer. – Jon Skeet Dec 30 '16 at 08:33
  • @JonSkeet: ...Shameful of me... I didn't observe that. I fought sleep to skim through Stackoverflow yester night. I guess I wasn't seeing sharp. I just reread your answer now. And I am totally embarrassed with myself... – WhiZTiM Dec 30 '16 at 09:08
  • @WhiZTiM: Not a problem at all :) – Jon Skeet Dec 30 '16 at 09:17