8

I just wrote this code:

private double PerformOperation(OperationEnum operation, double aggregateValue, 
                                double sourceValue)
{
    if (operation == OperationEnum.Sum)
        return aggregateValue + sourceValue;
    if (operation == OperationEnum.Subtract)
        return aggregateValue - sourceValue;
    if (operation == OperationEnum.Multiply)
        return aggregateValue * sourceValue;
    if (operation == OperationEnum.Divide)
        return aggregateValue / sourceValue;
    throw new InvalidOperationException("Unsupported Aggregation Operation");
}

It seems very repetitive. Is there a way to generalize this? So I don't have to have 4 lines that are the same except a different sign?

(Note: if there is a better way that does not use the OperationEnum that is great)

Vaccano
  • 78,325
  • 149
  • 468
  • 850
  • 1
    It's a small thing, but I like to put the string value of the unsupported enum option in the exception so it's obvious what value isn't being handled. – RQDQ Jan 21 '11 at 21:09
  • Where are you getting the OperationEnum from? – StriplingWarrior Jan 21 '11 at 21:09
  • You sure you don't want OOP here? inheritance? – Lasse V. Karlsen Jan 21 '11 at 21:11
  • @StriplingWarrior - I made it. It can be seen here if you want: https://gist.github.com/790434 I did not include it in the question because I did not want to clutter it. – Vaccano Jan 21 '11 at 21:11
  • @RQDQ, there might not be a string representation. Presumably, the enum has only those four options. You're not restricted to actually *using* one of those options when using the enum. For example, you could write `(OperationEnum)17`. – Anthony Pegram Jan 21 '11 at 21:12
  • I think the question is more along the lines of, is this operation argument value coming from a UI, a database, etc (or at least that's my question) – RQDQ Jan 21 '11 at 21:13
  • @Anthony Pegram, agreed, but throwing a operation.ToString() into the exception would provide some insight. – RQDQ Jan 21 '11 at 21:14
  • 1
    @Vaccano - The reason I ask is that if you're stuck with taking an OperationEnum, you won't be able to simplify this much further, but if you can change the way the method works, there might be more you can do. We'd need to see how you're using the method, though (i.e. how you're coming up with the OperationEnum value). – StriplingWarrior Jan 21 '11 at 21:18
  • @StriplingWarror - I plan to read it in from an xml file. Right now I have it hard coded in my code (I just am calling it with OperationEnum.Sum) – Vaccano Jan 21 '11 at 21:35
  • +1 Liked question occasionally – Alan Turing Jan 21 '11 at 21:52

8 Answers8

3

You can make a Dictionary<OperationEnum, Func<double, double, double>>:

static readonly Dictionary<OperationEnum, Func<double, double, double>> operations = 
    new Dictionary<OperationEnum, Func<double, double, double>> {
        { OperationEnum.Sum, (a, b) => a + b },
        ...
    };
SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
1

It looks about right to me, although I'd use a switch statement for this. But maybe I don't understand what you're trying to do?

switch(operation)
{
    case OperationEnum.Sum:      return aggregateValue + sourceValue;
    case OperationEnum.Subtract: return aggregateValue - sourceValue;
    case OperationEnum.Multiply: return aggregateValue * sourceValue;
    case OperationEnum.Divide:   return aggregateValue / sourceValue;
    default:
        throw new InvalidOperationException("Unsupported Aggregation Operation");
}

It's essentially the same, but at least it looks prettier.

Justin Morgan - On strike
  • 30,035
  • 12
  • 80
  • 104
  • 1
    Please explain why this was downvoted. It's possible that I didn't understand what OP wanted, but the question seems to be asking for improvements to the code block he posted. Without more information about his situation, prettifying the code seems like the only thing to do here. I clearly wasn't the only person who thought so, either. Perhaps the question needs to be more specific. Either way, a downvote with no explanation helps no one. – Justin Morgan - On strike Jan 21 '11 at 21:24
  • 1
    +1 - though this doesn't solve the repetitiveness, at least it's more readable and can be optimized by the compiler to use a `Hashtable` internally if there are many case statements. – BrokenGlass Jan 21 '11 at 21:37
  • Internally works at assembly level exacly the same as in question – Alan Turing Jan 21 '11 at 21:39
  • @Artur Mustafin: Not necessarily. The compiler will choose an intelligent implementation depending on the values assigned to the enum fields. This could end up using the same implementation as in the question, but it's likely that it will choose an even faster implementation. @Justin Morgan: Only reason I didn't upvote this is that @lasseespeholt beat you to this answer. – StriplingWarrior Jan 21 '11 at 22:11
1

You can create a dictionary of delegates, so your function will look something like:

private double PerformOperation(OperationEnum operation, double aggregateValue, 
                            double sourceValue)
{
    return operators[operation](aggregateValue, sourceValue);
}
Elalfer
  • 5,312
  • 20
  • 25
  • 3
    But this requires that you build the dictionary which makes it longer and maybe more unreadable than the current solution or a switch. – Lasse Espeholt Jan 21 '11 at 21:12
  • But it will be more "reusable" code, and sometimes less code doesn't mean better. – Elalfer Jan 21 '11 at 21:15
  • `return operators[operation](aggregateValue, sourceValue)` - what is it returning? what is `operators`? – VoodooChild Jan 21 '11 at 21:18
  • @Elalfer I would really like to understand your logic and improve my knowledge, as I have seen this approach work in various places. But in this case the asker still have to use `PerformOperation` and therefore it is not far from a switch (converts to array loopup). Maybe you think about the possibility to add features on runtime with this solution? – Lasse Espeholt Jan 21 '11 at 21:20
  • @VoodooChild As I said `dictionary of delegates`, and it should be quite easy to figure out how must it look like – Elalfer Jan 21 '11 at 21:21
  • I prefer the switch statements for enums, as the compiler will intelligently choose the fastest strategy (array, dictionary, or iterative) depending on the switch cases. If you wanted to make your operation list "extensible", the dictionary strategy can come in handy, but if you're switching on an enum you know at compile time exactly how many possible values there are and what you're going to do with each one. – StriplingWarrior Jan 21 '11 at 21:23
  • @lasseespeholt At the end you can just get rid of `PerformOperation` method and just use `operators[operation](aggregateValue, sourceValue)` in your code if you need it. – Elalfer Jan 21 '11 at 21:23
  • @Elafer: ok, you should edit your answer more to show a workable example related to the question. So in future someone can reference this post, because as it stands now - some of us might have difficulty figuring out this approach. – VoodooChild Jan 21 '11 at 21:24
  • @StriplingWarrior First of all question was `how to generalize`, and second, I'm not sure about performance. There are no strategy for `switch` or a lot of `if` and every wrong `if` will cause CPU mu-op cache clear. But if you create an array of delegates it could be even faster and looks nicer – Elalfer Jan 21 '11 at 21:26
  • @Elalfer Sure, but you need ~10 lines of code to build the dictionary and I (subjective) prefer calling a method like `PerformOperation` rather than use `operators[operation](aggregateValue, sourceValue)` in my code various places. By using `PerformOperation` the implementation details are hidden which allow me to replace the algorithms. I respect your opinion, though :) – Lasse Espeholt Jan 21 '11 at 21:29
  • 1
    @lassee, you need at most 5 lines of code to construct this dictionary. See SLaks answer for reference. Think 1 line for declaration, 4 lines for loading the `Func<,,>` values. Is it more readable? Your call. – Anthony Pegram Jan 21 '11 at 21:34
  • @Anthony ~10 lines with new lines for brackets etc. The 5 line feat can also be done with a switch, see Justin' answer. – Lasse Espeholt Jan 21 '11 at 21:37
  • Agree with Anthony. This is another reason why ppl prefer to write class factories instead of huge switches and use other templates, because it is supportable, reusable and quite easy to read. – Elalfer Jan 21 '11 at 21:37
  • 1
    dictionary of delegates works too slow than array access by index – Alan Turing Jan 21 '11 at 21:41
  • @Artur Read all my comments and you'll find `an array of delegates it could be even faster and looks nicer` – Elalfer Jan 21 '11 at 21:43
  • 2
    @Elalfer: I don't really see how this is "generalizing" the code, since you'll still need just as much repetitive code to populate the dictionary, as in SLaks's answer. Regarding performance, it is incorrect to say "there are no strategy for `switch`." The compiler will choose the best strategy, and use either a jump table, a binary decision tree, a dictionary, or a series of if/else statements depending on the case statements and the numeric values of each of the enum fields. With only 4 possible values, even if/else statements will be faster than a dictionary lookup. – StriplingWarrior Jan 21 '11 at 22:29
  • @StriplingWarrior: +1 -- 500% agree. :) – user541686 Jan 22 '11 at 06:18
1

Yes, using List<Func<double, double, double>>:

List<T> accessor works mush faster than Dictionary<> accessor, and comparable to switch statement ;)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace test
{
    class Program
    {
        enum OperationEnum : int { Sum, Muiltiply, Divide, Subtract }
        static List<Func<double, double, double>> actions = new List<Func<double, double, double>>()
        {
            { (a,b) => a+b },
            { (a,b) => a*b },
            { (a,b) => a/b },
            { (a,b) => a-b },
        };
        static void Main(string[] args)
        {
            Console.WriteLine(string.Format("{0}", actions[(int)OperationEnum.Sum](1, 3));
        }
    }
}
SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
Alan Turing
  • 2,482
  • 17
  • 20
  • 1
    @StriplingWarrior, it's a possible solution though, doesn't deserve a down-vote. – Filip Ekberg Jan 21 '11 at 21:12
  • 3
    @Filip Ekberg, @Artur Mustafin: First of all, I wasn't the one who down-voted it. Secondly, the "solution" at the time I wrote my comment was not an answer to the question. See http://stackoverflow.com/posts/4763840/revisions – StriplingWarrior Jan 21 '11 at 21:27
  • I hear you say a lot stuff about my solution not being short enough. But what happens to your solution if I do this `enum OperationEnum : int { Sum, Muiltiply = 0, ... }`? This means you would also have to use the 4 different `OperationEnum.X` somewhere like SLaks' solution. By the way, I haven't downvoted your answer - I'm just expressing my opinion on your solution (which is indeed a solution). – Lasse Espeholt Jan 22 '11 at 08:32
  • @lasseespeholt: If you do that, nothing will change at all, think about it. My solution will break, if any, at the same point as yours, if you will play with enums, moreover, i just initialized the list in declarative manner only for shortage, so if you think twice, you will understand, that you solution and mine are almost the same, but i eliminated the excessiveness, and you don't ;) – Alan Turing Jan 23 '11 at 01:43
0

Nope... it's not quite that generalizable, although I wish it was too. :\

user541686
  • 205,094
  • 128
  • 528
  • 886
  • 1
    This should have been a comment. Just because you don't have any concrete answers to the problem, doesn't mean there aren't any. – Filip Ekberg Jan 21 '11 at 21:20
  • 2
    But the fact of the matter is that *somewhere* you need to manually match the signs with the enum members by hand (it's not like the compiler does that). There's simply no way to avoid that, so there's no way to generalize this more. – user541686 Jan 21 '11 at 21:22
  • ... how? Isn't it a fact? Do you have any better idea? – user541686 Jan 21 '11 at 21:31
  • 1
    @Filip Ekberg: In addition to what Mehrdad said, it appears you also down-voted everybody who tried to provide a concrete answer, but failed to make it more general. I would rather have an "it can't be done" answer (if it can't be done) or a "you can improve it like so" answer than to have no answers at all. – StriplingWarrior Jan 21 '11 at 21:32
  • 1
    @Filip: The question was quite literally " **Is** there a way to generalize this?" And so I don't get why an answer of "No" is bad or argumentative in any way. – user541686 Jan 21 '11 at 21:34
  • @StriplingWarrior, I've up-voted the answers that I like and down-voted the ones that I don't think contribute anything to this thread. @Mehrdad, there are ways to generalize it, as others have proven. The question is if it is possible to generalize operations **like** this, and it is possible. Therefore I think your answer should be left as a comment instead. – Filip Ekberg Jan 21 '11 at 21:36
  • 1
    "This is impossible" is still an answer. If it's correct, it's a useful answer, because the questioner can save time trying to do the futile. I've seen such answers be upvoted, accepted, and appreciated. If he's wrong, that might deserve a downvote, but he should be proven wrong first. – Justin Morgan - On strike Jan 21 '11 at 21:37
  • 2
    @Filip: No, the question was "generalize this" and **not** "generalize operations like this". You misread it apparently, and downvoted everyone for not answering a different question. Also, you should realize that "questions like this" is *way* more subjective and vague than my answer is "argumentative". – user541686 Jan 21 '11 at 21:38
  • @Justin, sure there are times when the answer "No" might be correct. But methods to make it more general have been provided, therefore I don't think this answer deserves any upvotes. @Mehrdad, I meant that the question is argumentative. Not your answer. – Filip Ekberg Jan 21 '11 at 21:40
  • 1
    @Filip: [See this question](http://stackoverflow.com/questions/4643465/writing-a-cross-platform-32-bit-and-64-bit-compatible-program-for-windows-like) as an example of something whose answer was, simply, "this is not possible" (paraphrased), but that I completely liked and accepted, and that was upvoted. – user541686 Jan 21 '11 at 21:41
  • @Mehrdad, yes but that answer had a bit more information in it than "It's not possible". – Filip Ekberg Jan 21 '11 at 21:42
  • 1
    @Filip: (a) That's because the reason for the answer wasn't as straightforward as here. In this question, there simply *isn't any feature that permits more generalization*... there's not much to elaborate on. (b) I'd have accepted that question anyway even if all it said was "No; mscoree.dll is the .NET loader and not the framework."... I don't care if the answer is long or short, but whether or not it was to the point. (c) No one asked that you upvote anything (I can see why you wouldn't upvote this answer), but we were all just surprised why you downvoted the answers. – user541686 Jan 21 '11 at 21:46
  • Ok, I've stated why I think you deserve a down-vote, I shouldn't have to explain myself. If you answer with no "proof" of why it isn't possible, it's impossible to take this answer serious. I've up-voted three of the answers I liked here and down-voted one or two that I didn't like. – Filip Ekberg Jan 21 '11 at 21:48
  • @Filip: How can you "prove" that something doesn't exist?!?!? Do you want me to refer the OP to the entire hundreds of pages of the ECMA C# specification and "prove" it to him by telling him to read them, and observe that no solution exists?! – user541686 Jan 21 '11 at 21:51
  • @Mehrdad, Appearently someone succeded to do that in the thread you linked. Maybe saying "No it is not possible to generalize this more because you would need to store the operators in a dictionary that would defeat the purpose of generalization. It would lead to as much code no matter what you do". Something like that maybe? – Filip Ekberg Jan 21 '11 at 21:53
  • 1
    @Filip: Well, maybe my answer doesn't deserve an up-vote, but does it really deserve a *downvote*? It wasn't wrong or misleading, and from what I've observed on SO, you need to downvote things that spread misinformation, *not* things that you "don't like". – user541686 Jan 21 '11 at 21:55
  • We have different point of views on what to up-/down-vote. You upvote something that you want to put weight on, that provides something good to the thread. The opposite for down-voting. Instead of argumenting here weather or not your answer deserves a down-/up-vote, why not spend the time answering questions instead? :) – Filip Ekberg Jan 21 '11 at 21:58
  • 3
    "Above all, be honest. If you see **misinformation**, vote it down. *Add comments* indicating what, specifically, is wrong. **Provide better answers of your own**. Best of all — *edit* and **improve** the existing questions and answers!" (http://stackoverflow.com/faq) – user541686 Jan 21 '11 at 21:59
  • @Mehrdad, I think your question is misinformation. And I've told you why I think so. Appearently someone else thought the same.. It's up to every individual to up-/down-vote the answers that they do/don't like. I don't see why you take it so personally. Eventually if you have to argument on Every down-vote, no one is going to use them and they become pointless. – Filip Ekberg Jan 21 '11 at 23:15
  • 1
    @Filip: I don't take it personally (honestly, I up-voted one of your comments on another thread about half an hour ago!! I didn't take it personally :) ) but if I really spread misinformation, I want to know, so both I and others can learn something. Feel free to vote however you want, but I just wanted to know why you voted how you did, so that it could actually be constructive. – user541686 Jan 21 '11 at 23:18
  • I understand that, one of my comments I left contained an "example" of what I think would make the answer "better" and thus, maybe, would have deserved another voting. I rarely edit others answers more than correcting spelling errors, I rather tell the poster about it instead. :) – Filip Ekberg Jan 21 '11 at 23:22
  • @Filip: Well the issue was that you only commented on it *after* I asked you... usually you should comment on it yourself, without the answerer needing to ask why you downvoted. But whatever; it's not that important... don't worry, I didn't take it personally. :) – user541686 Jan 21 '11 at 23:24
  • @Mehrdad, It's just a guideline anyways in the FAQ, it's not like it's rocket science to figure out why one gets a downvote. – Filip Ekberg Jan 22 '11 at 10:03
0

A switch would be prettier at least :) and the code will be more efficient (array look-ups etc.). Furthermore, the compiler and IDE can give you some hints when you miss certain options etc.

But as stated in my comment, this isn't really an answer just general code improving.

switch (operation)
{
    case OperationEnum.Sum:
        return aggregateValue + sourceValue;
    case OperationEnum.Subtract:
        return aggregateValue - sourceValue;
    case OperationEnum.Multiply:
        return aggregateValue * sourceValue;
    case OperationEnum.Divide:
        return aggregateValue / sourceValue;
    default:
        throw new InvalidOperationException("Unsupported Aggregation Operation");
}
Lasse Espeholt
  • 17,622
  • 5
  • 63
  • 99
  • 3
    I respect a downvote - but please state why you(?) do so - I could have a fair argument... – Lasse Espeholt Jan 21 '11 at 21:16
  • How is this more general than the if-setup? – Filip Ekberg Jan 21 '11 at 21:18
  • @Filip Thanks :) It is not... however a switch do come with certain advantages from the IDE and the compiled code itself. But I see your point and this isn't really an answer but something that should go in a comment - I'll consider deleting this answer... – Lasse Espeholt Jan 21 '11 at 21:25
  • @lasseespeholt: Internally it works at assembly level exacly the same as in question – Alan Turing Jan 21 '11 at 21:45
  • 1
    @Artur It will properly be optimized to the same array lookup (if that is the fastest solution) in this case but at least I know that will happen with the switch because a switch is very strict and therefore the compiler can reason about it easier. In the general case you can't know for sure because a compiler can't know what you precisely are doing when we speak of turing-complete languages. – Lasse Espeholt Jan 21 '11 at 21:51
  • @lasseespeholt: Still thinks, that { (a,b) => a+b } is much more readable ,than case OperationEnum.Sum: return aggregateValue + sourceValue; – Alan Turing Jan 21 '11 at 21:59
  • @Artur Mustafin: `{ (a,b) => a+b }` is shorter, but I don't find it more readable, and it's prone to break without any compiler warnings if there is ever a change to the OperationEnum class (e.g. if the fields are reordered). – StriplingWarrior Jan 21 '11 at 22:07
  • @Artur, if you like I can change the parameter names ;) that leaves me with `OperationEnum.Plus: return a + b`; – Lasse Espeholt Jan 22 '11 at 08:24
0

This should do it, however, I believe a switch statement would be the more efficient.

private double Evaluate(string expression)
{
    return (double)new System.Xml.XPath.XPathDocument
    (new System.IO.StringReader("<r/>")).CreateNavigator().Evaluate
    (string.Format("number({0})", new
    System.Text.RegularExpressions.Regex(@"([\+\-\*])")
    .Replace(expression, " ${1} ")
    .Replace("/", " div ").Replace("%", " mod ")));
}

OR

private double Evaluate(string expression)
{
    System.Data.DataTable table = new System.Data.DataTable();
    table.Columns.Add("expression", string.Empty.GetType(), expression);
    System.Data.DataRow row = table.NewRow();
    table.Rows.Add(row);
    return double.Parse((string)row["expression"]);
}

Then...

public double PerformOperation(double x, double y, string op)
    {
        string exp = string.Format("{0} {1} {2}"
                    , x.ToString()
                    , op, y.ToString());

        return Evaluate(exp);
    }
Chris Gessler
  • 22,727
  • 7
  • 57
  • 83
0

I'm not sure of the context of this, but rather than the enum you could use a delegate that performs the desired operation to be passed into your "PerformOperation"-method. However you probably don't even need that static method if you would use this approach. Another benefit is that you don't have to care about the special case where the operation is not supported, any operation is supported. Here's a quick example:

namespace Example
{
    using System;

    public delegate double Operation(double first, double second);

    public static class Operations
    {
        public static readonly Operation Sum = (first, second) => first + second;
        public static readonly Operation Subtract = (first, second) => first - second;
        public static readonly Operation Multiply = (first, second) => first * second;
        public static readonly Operation Divide = (first, second) => first / second;
    }

    class Program
    {
        static void Main(string[] args)
        {
            double seed = 0;
            double aggregateValue = 0;

            aggregateValue = PerformOperation(Operations.Sum, 5, 10);
            Console.WriteLine(aggregateValue);

            aggregateValue = PerformOperation(Operations.Multiply, aggregateValue, 10);
            Console.WriteLine(aggregateValue);

            aggregateValue = PerformOperation(Operations.Subtract, aggregateValue, 10);
            Console.WriteLine(aggregateValue);

            aggregateValue = PerformOperation(Operations.Divide, aggregateValue, 10);
            Console.WriteLine(aggregateValue);

            // You can even pass delegates to other methods with matching signatures,
            // here to get the power of ten.
            aggregateValue = PerformOperation(Math.Pow, aggregateValue, 10);
            Console.WriteLine(aggregateValue);

            Console.ReadLine();
        }

        private static double PerformOperation(Operation operation, double aggregateValue, double sourceValue)
        {
            return operation(aggregateValue, sourceValue);
        }
    }
}

Using this approach you can easily extend your program with new operations without even touching the PerformOperation-method. This can be taken quite a few steps further but without knowing the context it's hard to describe.

Patrik Hägne
  • 16,751
  • 5
  • 52
  • 60