3

I'm wondering if this is possible to do in c#.

Let's say i have a class with these methods:

public class Ladder
{
    private int currentStep = 0;

    Ladder Up()
    {
        currentStep++;
        return this;
    }

    Ladder Down()
    {
        currentStep--;
        return this;
    }
}

I can use it like this:

Ladder ladder = new Ladder();

ladder.Up().Up().Up().Down().Down();

Now, I would like to add a conditional method, that could be used like this:

ladder.IF(somecondition, Up(), Down());

Meaning if somecondition == true then execute Up(), else Down()

Is it possible to do? I was thinking about using anonymous methods, but can't figure out how to reference "this" instance so it would know what that these functions are referred to.

Any help is greatly appreciated!

Vadim
  • 394
  • 1
  • 7
  • You don't need a method as Romano has indicated in his answer, but this question/answer might lead you towards what you are talking about (even though quite unnecessary for what you are doing): https://stackoverflow.com/questions/2082615/pass-method-as-parameter-using-c-sharp – CodeLikeBeaker Jun 01 '17 at 13:41
  • So you want a method with an `If(bool, Action, Action)` signature? E.g. `ladder.If(cond, () => ladder.Up(), () => ladder.Down())`? Or expressions, like `ladder.If(cond, l => l.Up(), l => l.Down())?`? It's really unclear what exactly you want, how, and what problem it should solve. You can't change the syntax rules of C#. What you show are method calls, which are executed unconditionally. And yes, this comment was written with an undertone that should display my dislike for "funny" (read: unclear) syntax such as "fluent" code. – CodeCaster Jun 01 '17 at 13:45
  • 1
    It's a really bad idea to use a pattern like this in the first place. When a method returns a new value it strongly implies that the method isn't mutating the object, but rather creating a new one. You should *either* make the object immutable, and return a new object above or below it in these methods, or mutate the object *and not return anything* to effectively communicate to a caller that the method is to be called for its side effects. – Servy Jun 01 '17 at 13:55
  • @Servy but `ladder.Up().Up().Up().Down().Down();` looks so cool! Fluent syntax has its uses, but should really be used sparingly. Again, IMHO. – CodeCaster Jun 01 '17 at 13:58
  • 1
    @CodeCaster Sure, and whenever you see that code you would expect that each of those method calls is creating a new object, not mutating another object, thus one would expect that after that statement `ladder` would still be in the same position. – Servy Jun 01 '17 at 14:01
  • To give more context to my question, I'm trying to implement a version of "turtle graphics". https://en.wikipedia.org/wiki/Turtle_graphics So I can chain the commands like this: turtle.Move(20).Rotate(30).Move(40) – Vadim Jun 01 '17 at 15:00

6 Answers6

5

This would probably be close to what you're asking, although I wouldn't say it will make the code clearer to future maintainers:

Ladder If(bool condition,
    Func<Ladder, Ladder> @true, 
    Func<Ladder, Ladder> @false)
{
    return condition ? @true(this) : @false(this);
}

Usage:

ladder
    .Up()
    .Down()
    .If(true, l => l.Up(), l => l.Down())
    .Up()
    .Down();
vgru
  • 49,838
  • 16
  • 120
  • 201
  • 1
    Upvoted for _"I wouldn't say it will make the code clearer to future maintainers"_, even though tempting not to vote at all since it is a ridiculously unclear question. – CodeCaster Jun 01 '17 at 13:46
  • Clearly the best answer! – Romano Zumbé Jun 01 '17 at 13:52
  • Besides, you can pass _any_ expression that accepts and returns a ladder for the `Func`s, so compile-time safety is limited. You could pass `l => new Ladder()`, `l => null`, and so on. – CodeCaster Jun 01 '17 at 13:57
  • This is exactly what i was looking for. Thank you very much! I agree that this syntax is not very clear and hard to maintain, but in my project being able to write short expressions is crucial. It can be always done in conventional way, but it's great to have this option. – Vadim Jun 01 '17 at 14:17
  • This is just a way to do what you're asking. Perhaps a slightly better signature would be `If(Func condition, ...)` so that the condition is evaluated using the parent ladder instance too. Or, a general way might even be to have a generic extension method (`If(Func condition, Func onTrue, Func onFalse)`). – vgru Jun 02 '17 at 07:30
  • The syntax is not even that convoluted IMHO, **however** one thing that I usually expect from fluent methods is that they create a new instance on each call, i.e. don't mutate the original instance. Or even better, create an expression tree (a new instance on each call, like `IQueryable` extension methods work), which is serializable (e.g. easily supports undo operations, replaying parts of the movement and similar stuff). It depends on your actual goals, of course. – vgru Jun 02 '17 at 07:32
3

You don't need a conditional method. Use the ?: operator:

somecondition ? ladder.Up() : ladder.Down();
Romano Zumbé
  • 7,893
  • 4
  • 33
  • 55
  • 1
    The problem is, this is clearly a fluent interface, and this way kinda destroys that - as you cant (easily) chain on other calls. Except with the clunky looking `(somecondition ? ladder.Up() : ladder.Down()).Down().Up()` – Jamiec Jun 01 '17 at 13:45
  • Yes, the point is to be able to chain the commands. For example: ladder.Up().Up().Up().IF(a==1, Up(), Down()).Up().Up(); – Vadim Jun 01 '17 at 13:50
2

You could add another method Go which encapsulates your requirement, you have 2 options - perhaps implement both:

public Ladder Go(bool condition)
{
    if(condition)
      Up();
    else
      Down();
    return this;
}

public Ladder Go(Func<bool> condition)
{
    return this.Go(condition());
}

This allows you to specify a boolean, inline a condition, or pass a function

ladder.Go(true);
ladder.Go(someValue > 0);
ladder.Go( someMethodWhichTakesNoParamsAndReturnsBool )
Jamiec
  • 133,658
  • 13
  • 134
  • 193
  • In reality there are many more methods, not just Up() and Down(), having different signatures, parameters, etc. It would be too confusing to have them combined into one method. – Vadim Jun 01 '17 at 13:54
  • 1
    @Vadim ah right - ask the right question, you'll get the right answer. Ask a cut down question, you'll get a cut down answer. Now, how about you specify what you're *really* trying to do? – Jamiec Jun 01 '17 at 13:56
2

This is the way to write this method :

public Ladder If(bool someCondition, Func<Ladder> trueFunc, Func<Ladder> falseFunc)
    {
        return someCondition ? trueFunc() : falseFunc();
    }

And the way to call it is

ladder.If(condition, ladder.Up, ladder.Down);
Aboc
  • 237
  • 1
  • 5
  • This would be great, but it doesn't seem to be working for me. Gives a syntax error. – Vadim Jun 01 '17 at 14:25
  • @Vadim There's nothing technically wrong with this code, what error do you get? – DavidG Jun 01 '17 at 14:36
  • @DavidG Sorry, you are right - this works! Problem is that my code is more complicated. For example if there's another method `Ladder Jump(int steps)` - I cannot call it, because it accepts arguments and `Func` works only for methods with no arguments. Is there a way to make it generic, so I could call with any number of arguments? Like this: `ladder.If(condition, ladder.Up, ladder.Jump(5));` – Vadim Jun 01 '17 at 15:10
  • 1
    In this case, you can write like this: `ladder.If(condition, ladder.Up, () => ladder.Jump(5));` – Aboc Jun 01 '17 at 15:17
  • @Aboc thank you! this works! I'm trying to figure out if there's any difference between using this way and the one suggested by Groo above? It seems there is only difference in syntax: `()=>ladder.Jump(5)` instead of `x=>x.Jump(5)` but otherwise it's doing exactly same thing, correct? – Vadim Jun 01 '17 at 15:38
  • @Vadim: the differences are that `Func` always passes the "parent" (`this`) instance to the delegate, so it should help prevent passing an incorrect instance (although nothing really prevents you from doing `x => someOtherLadder.Up()`). On the other hand, `Func` will always require `ladder` to be passed as a closure, which means two additional instantiations for each call to `If`. – vgru Jun 02 '17 at 07:19
  • @Vladim my solution is just more clean when using parameterless methods, but is less homogeneous when mixing methods with different signatures. – Aboc Jun 02 '17 at 08:41
0

This is the closest I could get to what you want - there is an extension method called If which takes actions to perform on the ladder. These actions are defined in your Ladder class as static methods. Then you use the static import functionality to import your Ladder class.

// This import is really important - it lets you refer to Up, 
// rather than Ladder.Up.
using static Ladder;

void Main()
{
    Ladder ladder = new Ladder();

    // Basic usage
    ladder.If(true, Up, Down);
    Console.WriteLine(ladder.ToString());

    // And demonstrating chaining.
    ladder.If(false, Up, Down).If(false, Up, Down);
    Console.WriteLine(ladder.ToString());
}

public class Ladder
{
    private int currentStep = 0;

    public Ladder Up()
    {
        currentStep++;
        return this;
    }

    public Ladder Down()
    {
        currentStep--;
        return this;
    }

    // Static methods for Up and Down a ladder.
    public static void Up(Ladder ladder)
    {
        ladder.Up();
    }

    public static void Down(Ladder ladder)
    {
        ladder.Down();
    }

    public override string ToString()
    {
        return "I'm on step " + currentStep;
    }
}


public static class Extensions
{
    // Extension method to conditionally take an action on the ladder.
    public static Ladder If(this Ladder ladder, bool condition, Action<Ladder> trueAction, Action<Ladder> falseAction)
    {
        if (condition)
        {
            trueAction(ladder);
        }
        else
        {
            falseAction(ladder);
        }

        return ladder;
    }
}
RB.
  • 36,301
  • 12
  • 91
  • 131
  • Great demonstration of `using static` for (un)intentional code obfuscation. – CodeCaster Jun 01 '17 at 13:47
  • Obfuscation? I don't know what you mean :P I actually start all my unit tests with `using static XUnit.Assert;`, then I can just put code like `NotNull(x);` or `Equal(5, theResult);`. I love static imports (although my colleagues hate me!) – RB. Jun 01 '17 at 13:49
  • 2
    _"although my colleagues hate me!"_ - exactly! All it does is making code harder to read. IMHO of course. It has its (localized) use, but is easy to abuse. – CodeCaster Jun 01 '17 at 13:50
  • 1
    Yeah - I tend to restrict it's use to unit-tests where I think it actually does make it more readable (if used consistently). Sometimes I will import `System.DayOfWeek` and similar enums as well - I think it can make code more readable when used *judiciously*. – RB. Jun 01 '17 at 13:52
0

You can add another method in your Ladder class (with : using System.Reflection; )

public Ladder IF(bool condition, string trueFunc, string falseFunc)
{
    MethodInfo trueMethod = this.GetType().GetMethod(trueFunc);
    MethodInfo falseMethod = this.GetType().GetMethod(falseFunc);
    return condition ? (Ladder)trueMethod.Invoke(this, null) : (Ladder)falseMethod.Invoke(this, null);
}

And use it like that :

ladder.IF(true, "Up", "Down"); // goes up
ladder.IF(false, "Up", "Down"); // goes down
ladder.IF(true, "Down", "Up"); // goes down
ladder.IF(false, "Down", "Up"); // goes up
DKH
  • 452
  • 7
  • 15