39

I have upgraded my project to target C# 7 and used Visual Studio 2017 RC to implement pattern matching across my solution. After doing this some errors were introduced relating to pattern matching with generic parameters.

Consider the following code:

public class Packet
{
}

public class KeepalivePacket : Packet
{
}

public void Send<T>(T packet)
    where T : Packet
{
    if (packet is KeepalivePacket keepalive)
    {
        // Do stuff with keepalive
    }

    switch (packet)
    {
        case KeepalivePacket keepalivePacket:
            // Do stuff with keepalivePacket
            break;
    }
}

Both the if statement and the case statement produce a compilation error.

An expression of type T cannot be handled by a pattern of type KeepalivePacket

If I first cast the parameter to type object the pattern matching works as expected. Roslyn then marks the cast to object as redundant.

if ((object)packet is KeepalivePacket keepalive)
{
    // This works
}

This error only appears to apply to generic parameters and variables. Roslyn appears to not be aware of this issue as it recommends changing the code to use pattern matching via an analyzer and allows me to apply the "code fix" resulting in the broken code.

Alex Wiese
  • 8,142
  • 6
  • 42
  • 71
  • It looks like *bad design* if you have to switch on the type of an object. Actually the `is` operator is by some already considered to be a *bad smell*... – Willem Van Onsem Jan 05 '17 at 22:14
  • 6
    @WillemVanOnsem there must be enough use cases for pattern matching for the C# team to add it to the specification, no? – Alex Wiese Jan 05 '17 at 22:22
  • indeed, simply because in most industrial applications, one cannot first design the entire thing: it is too complex. But actually every `is` operator, etc. is better replaced by dynamic bindings, since these are more typesafe. – Willem Van Onsem Jan 05 '17 at 22:24
  • 6
    @WillemVanOnsem I see, can you point me to an example of replacing the use of `is`/`as` operator with dynamic binding in C#? Or are you referring to dynamic dispatch? In the case of the code above the packet is being returned by a serializer, and control flow is achieved by switching on the type. – Alex Wiese Jan 05 '17 at 22:32

3 Answers3

26

As explained by Neal Gafter from Microsoft:

The reason it doesn’t work is that there is no conversion (explicit or implicit) defined from T to KeepalivePacket. Pattern matching requires such a conversion to exist, as it is defined in terms of the cast operator, which requires a conversion exist. The language specification and compiler agree that no conversion exists. It seems strange to me that the language specification is defined such that no (explicit) conversion exists here. We'll look at what we can do about that.

We're not going to do anything about this in C# 7. You'll have to add a cast to your code to work around it. Once we have recursive patterns, this may be more difficult to work around. Moreover, the awkward language rule that underlies this issue (i.e. that there is no conversion from T to KeepalivePacket) doesn't make a lot of sense.

Update

This is now working in C# 7.1

Community
  • 1
  • 1
Alex Wiese
  • 8,142
  • 6
  • 42
  • 71
12

C# 7.1 supports this now. For example, see "Pattern Matching with Generics" in this article. You may need to add <LangVersion>7.1</LangVersion> or <LangVersion>latest</LangVersion> to your project file. See here for details on configuring LangVersion.

Nate Cook
  • 8,395
  • 5
  • 46
  • 37
4

The answer to C#7.0 is

if ((Packet)packet is KeepalivePacket keepalive)
{
    // Do stuff with keepalive
}

switch ((Packet)packet)
{
    case KeepalivePacket keepalivePacket:
        // Do stuff with keepalivePacket
        break;
}
CervEd
  • 3,306
  • 28
  • 25
  • 2
    not sure why this got downvoted, if you're stuck on C#7.0 this is how you have to solve it. If you're able to use a more modern C# version then by all means use pattern matching like you would in a normal language – CervEd Jan 07 '21 at 13:52
  • 1
    Your solution resonated with our situation. We are stuck on 7 right now, so your answer has come in handy. Now the question still remains there is a smell about the need to have this arrangement in the first place... but that is a problem for another day... I have not put my finger on it yet anyway... so that technical debt can go to the bank and accrue interest. (viscously self aware my code ain't the greatest. ;D) – Hunter-Orionnoir Nov 08 '22 at 08:21