1

I'm trying to understand writing anonymous Methods in C#. But having trouble to achieve a success.

Please have a look at my example. I'm trying to fill a property named Value by given conditions.

When I would write a private helper method which takes an inputparam int lockCardStatus and returns a string that would be a solution but my intention is to try this with a "quickshot" like so:

MailMessageTextPlaceholders = new List<MailMessageTextPlaceholder>
{
    new MailMessageTextPlaceholder
    {
        PlaceholderName = "status",
        Value = y => 
        {
            switch (_lockCard.Status)
            {
                case (int)LockCard.LockCardStatus.Done :
                    return "Is Done";
                case (int)LockCard.LockCardStatus.Pending :
                    return "Is in Pending status";
                case (int)LockCard.LockCardStatus.Open :
                    return "Has been created";
                default:
                    return "No status yet!";
            }
        }
    }
}

Unfortunately the compiler says:

Lambda Expression cannot be converted to type string because it is not a delgate.

Mong Zhu
  • 23,309
  • 10
  • 44
  • 76
Sum1Unknown
  • 1,032
  • 1
  • 9
  • 14
  • What is the definition of `MailMessageTextPlaceholder`? Specifically, what data type is `MailMessageTextPlaceholder.Value`? – canton7 Mar 21 '19 at 12:56
  • 3
    You probably meant to also invoke the delegate - at the moment you're just defining it, and then trying to assign it to `Value`. But it looks like `Value` wants a string, not a delegate. You probably wanted to do `new Func(() => { switch ....... })()`, which is really quite wordy and ugly. I'd just go with the helper method. In C# 8 we'll get switch expressions, which will let you do `Value = _lockCard.Status switch { .... }` – canton7 Mar 21 '19 at 13:02
  • 1
    If you just want to fill a property `Value` of type `string`, then you don't need an anonymous method here. Go with your helper method way. – dymanoid Mar 21 '19 at 13:02
  • 1
    A clean option would be to add an extension method to LockCardStatus, then the code could just be `Value = _lockCard.Status.ToOutputString()` – stuartd Mar 21 '19 at 13:02
  • Possible duplicate of [Cannot convert lambda expression to type 'object' because it is not a delegate type](https://stackoverflow.com/questions/23247125/cannot-convert-lambda-expression-to-type-object-because-it-is-not-a-delegate-t) – Mong Zhu Mar 21 '19 at 13:03
  • 1
    @monsee, Do you have the using System.Linq; in your file/code? – Dimitri Mar 21 '19 at 13:06
  • where does `_lockCard` come from? private class variable? – Mong Zhu Mar 21 '19 at 13:06
  • Ok, MailMessageTextPlaceHolder.Value is of type string. "y" was just an idea by myself that the returning Value would be returned into it and then assigned to Value. – Sum1Unknown Mar 21 '19 at 13:06
  • 1
    Do you think your current code is assigning a string to it? What do you think `y =>` does? – mjwills Mar 21 '19 at 13:07
  • 1
    I don't think the `switch` block in an anonymous function is the approach you want to use. `switch` statements are often (but not always) code smells. The example code you provided is difficult to maintain because it violates the Open-Closed Principal and requires you to edit your code every time you need to add a new status. I would recommend refactoring this into a `Dictionary` that is loaded from the database or a config file. That way you don't need to change any code or recompile/redeploy to add new items. – Lews Therin Mar 21 '19 at 13:15
  • According to your suggestions I think that using an helper method or the extension method would be the best way. Also it has the comfort when I need to use this "translator"-method several times to not repeat my code. – Sum1Unknown Mar 21 '19 at 13:17
  • 1
    If you need your `MailMessageTextPlaceholder` to know its `LockCardStatus` by the time your careate an instance of `MailMessageTextPlaceholder` , why not ask for `LockCardStatus` in the constructor of `MailMessageTextPlaceholder` ? – Oerk Mar 21 '19 at 15:08
  • @LewsTherin: In my coding experience, `switch` and `enum` are often used when the `enum` contains states which are part of business logic. Adding / removing states will probably lead to some rewrite anyway, so adding a state is many times not a matter of just adding it to the database. But you have a different opinion? That said, the translation of `enum` values to `string`s the resulting strings may need to be localized and the translations may need to be put in a database or resource files. – mortb Mar 22 '19 at 08:47
  • @mortb The technical debt associated with `switch` statements go hand-in-hand with `enum`s (because `enums` are often enumerated with a `switch` statement). `enum`s should only be used when you know all possible values of the constants and they aren't going to change (like the names of the days of the week for example). Anything that might change should be read from a config file or database. The [`enum` documentation](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/enum#robust-programming) has a section discussing this as well. – Lews Therin Mar 22 '19 at 14:54
  • @LewsTherin: I would not say that you are wrong, but I find your comments a bit vague. Do you have a link to any source to quote that would describe some alternative? Switch statements were elaborated in C# 7.0 and will see further development in C#8. To me, calling them code smells seems a bit dismissive – mortb Mar 25 '19 at 09:24
  • @mortb I mentioned that they are only a code *smell* because developers tend to misuse enums for naming constants that have a potential to change. That's not to say you should never use them, just that you should carefully consider how you are using them. The enum documentation I linked above discusses this "versioning" issue with enums (i.e. if you add a new enum value, any consumers of the enum will need to be updated). If the data you are wrapping in an enum is likely to change (like an enum of job titles), it should be loaded dynamically from a source outside your application. – Lews Therin Mar 25 '19 at 16:14
  • @LewsTherin: yes changing enum values in, for example, a public API might be painful – mortb Mar 27 '19 at 08:02

2 Answers2

3

The error arises because the compiler interprets this line:

Value = y => {...}

as an assignment. It thinks you want to assign the lambda expression to Value. But the types don't match! Value is a string and not a delegate of any kind. For detailed information see Compiler Error CS1660

What you actually want is to execute this lambda and assign the resulting value. To accomplish that you can define the delegate and the return value at creation of the lambda using Func<string>. and execute it on the spot by using the ( ) parentheses like in a normal method call:

MailMessageTextPlaceholder hodler = new MailMessageTextPlaceholder()
    {
        Value = new Func<string>(() =>
        {
            switch (_lockCard.Status)
            {
                case (int)LockCard.LockCardStatus.Done:
                    return "Is Done";
                case (int)LockCard.LockCardStatus.Pending:
                    return "Is in Pending status";
                case (int)LockCard.LockCardStatus.Open:
                    return "Has been created";
                default:
                    return "No status yet!";
            }
        })() // <- this executes the method!
    };

And suddenly this stuff becomes compileable.

EDIT:

Apparently the compiler is not able to infer the return type and thus specify the delegate type on its own. This can be illustrated by this example:

var asd = () => { return "XXX"; };

This line results in the error:

CS0815 Cannot assign lambda expression to an implicitly-typed variable

But excplicit specification of the delegate type resolves this error:

var asd = new Func<string>(() => { return "XXX";});

This indicates that the explicit specification of the delegate type is necessary and essential.

Mong Zhu
  • 23,309
  • 10
  • 44
  • 76
  • 1
    Mong Zhu, thanks for your explanation. Just tried it and it works. This is for the learning stuff and helps me to understand the resulting compiler error. Voted up :-) Thanks. – Sum1Unknown Mar 21 '19 at 13:26
  • you're very welcome. Glad to help, in the beginning I thought, that it should also work without `new Func` but apparently it doesn't. But I am still looking ;) I tell you, if I find another syntactical way of writing this – Mong Zhu Mar 21 '19 at 13:30
  • "*I thought, that it should also work without new Func but apparently it doesn't. But I am still looking ;)*" -- No it won't. A lambda expression has no type (is it a `Func` or some other delegate type which takes nothing and returns a struct?). Without that information you can't create the delegate instance, and therefore you can't invoke it. – canton7 Mar 21 '19 at 13:42
  • 1
    This is an interesting construct. However, I would find a helper method more readable, but I guess that's about personal preferences :) – mortb Mar 21 '19 at 13:58
  • @canton7 the compiler is sometimes so smart in infering types or return types, I hoped that in this case it would be able to infer the delegate type, but it complains with a [CS0149Error](https://learn.microsoft.com/en-us/dotnet/csharp/misc/cs0149) – Mong Zhu Mar 21 '19 at 15:28
  • 1
    @mortb I was very curious about the error and how to resolve it, but personally I would not write such a construct into productive code. – Mong Zhu Mar 21 '19 at 15:31
  • @monsee no it is not possible without the explicit specification, I edited my post and added an example which I think shows that – Mong Zhu Mar 21 '19 at 15:50
  • @canton7 you are correct, I found an example to illustrate this lack of inference from the compiler. – Mong Zhu Mar 21 '19 at 15:51
  • 1
    @MongZhu: it is always exiting to learn new things about the language :) – mortb Mar 21 '19 at 15:55
  • 1
    @MongZhu `public delegate string Foo(); public delegate string Bar();` -- those are separate and distinct types (although you can convert between them), but they've got the same signature. You can write `Foo x = () => "x"` and `Bar x = () => "x"`, and the compiler does infer that the lambda in the first is a Foo and is a Bar in the latter. But if you've got a case where the type of the lambda isn't mentioned, the compiler doesn't assume it's `Foo` or `Bar` or `Func` or anything else. Interestingly, the VB.net compiler will assume it's a `Func` - Func and Action are special – canton7 Mar 21 '19 at 16:17
2

What you are trying to do is not available (yet) in C# since switch statements cannot be evaluated unambiguously to one return value. A reason for this is that switch cases may contain any arbitrary code and not just one expression.

There will be a new way of writing switch statements in C# 8 that makes your code possible.

You may solve your problem by creating a helper method:

MailMessageTextPlaceholders = new List<MailMessageTextPlaceholder>
{
    new MailMessageTextPlaceholder
    {
        PlaceholderName = "status",
        Value = GetStatusDescription(_lockCard.Status)
    }
}

string GetStatusDescription(LockCard.LockCardStatus status)
{
     switch (status)
     {
        case (int)LockCard.LockCardStatus.Done :
             return "Is Done";
        case (int)LockCard.LockCardStatus.Pending :
             return "Is in Pending status";
        case (int)LockCard.LockCardStatus.Open :
              return "Has been created";
        default:
              return "No status yet!";
    }
}

and the helper method may be a local function

If you still really, really wish to inline your string mapping you may nest ternary operator statements.

MailMessageTextPlaceholders = new List<MailMessageTextPlaceholder>
{
    new MailMessageTextPlaceholder
    {
        PlaceholderName = "status",
        Value = 
           status == LockCard.LockCardStatus.Done
               ? "Is Done";
               : status == LockCard.LockCardStatus.Pending 
                  ? "Is in Pending status"
                  : status == LockCard.LockCardStatus.Open 
                      ? "Has been created"
                      : "No status yet!";
    }
}

When you are using ternary operators, C# will be able to unambiguously evaluate the result, which makes the compiler happy.

You may also solve your problem by adding description attributes to your enum

mortb
  • 9,361
  • 3
  • 26
  • 44