172

I have a class Animal, and its subclass Dog. I often find myself coding the following lines:

if (animal is Dog)
{
    Dog dog = animal as Dog;    
    dog.Name;    
    ... 
}

For the variable Animal animal;.

Is there some syntax that allows me to write something like:

if (Dog dog = animal as Dog)
{    
    dog.Name;    
    ... 
}
Erik Philips
  • 53,428
  • 11
  • 128
  • 150
michael
  • 1,849
  • 2
  • 13
  • 5
  • None that I know of. Any reason not to move Name up to Animal? – AlG Aug 18 '11 at 19:59
  • 23
    Just a note, code like can often be the result of breaking one of the [SOLID Principles](http://en.wikipedia.org/wiki/SOLID_(object-oriented_design)). The [L - Liskov Substitution Principle](http://stackoverflow.com/questions/56860/what-is-the-liskov-substitution-principle). Not saying it's wrong to do what you are doing all the time, but might be worth thinking about. – ckittel Aug 18 '11 at 20:03
  • please take note of what @ckittel is doing, you probably don't want to do this – khebbie Aug 19 '11 at 12:43
  • In some languages, including C#, I believe, `null` is cast to `false`. So, if the result of the assignment is null, the statement evaluates to `null` which is then cast to `false`. That is why in some languages you can use `if(var) {...}` to execute code only if that variable is non-null. Generally, assigning to variables in an `if` statement is poo-pooed because it looks like a common error (typing `=` instead of `==`). One used to be able to gain some (small) performance benefit from using an assignment in an `if` block, but many modern compilers will do optimizations like this for you. – wprl Aug 23 '11 at 18:02
  • Just a note: In PHP for instance you can do `if($var = )` and then `$var` is casted to `bool` and used for the check. `$var` is than also available in the scope where the `if` statement is. – thwd Aug 24 '11 at 09:35
  • 2
    @Solo no, `null` != `false` in C#; C# only allows actual bools or things implicitly convertible to bools in `if` conditions. Neither nulls nor any of the integer types are implicitly convertible to bools. – Roman Starkov Aug 24 '11 at 17:14
  • @romkyns thanks, I was hoping someone would correct me if I was wrong. It's been a year and a half since I've done any C#... what I said is however generally true (e.g. in C/C++) – wprl Aug 24 '11 at 17:49
  • This is one of the (multiple) problems I have with C#. To avoid polluting the scope with a variable used only in the `if` clause, I wrap everything is a scope, which is more verbose, and almost as ugly than the `for(...)` solution of Jon Skeet, marked `EVIL EVIL EVIL` for some reason... Aaah, sweet Inquisition... ^_^ ... – paercebal Nov 01 '11 at 14:22
  • possible duplicate of [C# - Is there a better alternative than this to 'switch on type'?](http://stackoverflow.com/questions/298976/c-sharp-is-there-a-better-alternative-than-this-to-switch-on-type) – nawfal May 18 '13 at 05:28
  • And here we are with [C#7](https://blogs.msdn.microsoft.com/dotnet/2016/08/24/whats-new-in-csharp-7-0/) and new syntax. I'm out of here though, time to learn native C++ and use a language with a more refined standard ^-^ – Sergey.quixoticaxis.Ivanov May 22 '17 at 15:19

21 Answers21

373

The answer below was written years ago and updated over time. As of C# 7, you can use pattern matching:

if (animal is Dog dog)
{
    // Use dog here
}

Note that dog is still in scope after the if statement, but isn't definitely assigned.


No, there isn't. It's more idiomatic to write this though:

Dog dog = animal as Dog;
if (dog != null)
{
    // Use dog
}

Given that "as followed by if" is almost always used this way, it might make more sense for there to be an operator which performs both parts in one go. This isn't currently in C# 6, but may be part of C# 7, if the pattern matching proposal is implemented.

The problem is that you can't declare a variable in the condition part of an if statement1. The closest approach I can think of is this:

// EVIL EVIL EVIL. DO NOT USE.
for (Dog dog = animal as Dog; dog != null; dog = null)
{
    ...
}

That's just nasty... (I've just tried it, and it does work. But please, please don't do this. Oh, and you can declare dog using var of course.)

Of course you could write an extension method:

public static void AsIf<T>(this object value, Action<T> action) where T : class
{
    T t = value as T;
    if (t != null)
    {
        action(t);
    }
}

Then call it with:

animal.AsIf<Dog>(dog => {
    // Use dog in here
});

Alternatively, you could combine the two:

public static void AsIf<T>(this object value, Action<T> action) where T : class
{
    // EVIL EVIL EVIL
    for (var t = value as T; t != null; t = null)
    {
        action(t);
    }
}

You can also use an extension method without a lambda expression in a cleaner way than the for loop:

public static IEnumerable<T> AsOrEmpty(this object value)
{
    T t = value as T;
    if (t != null)
    {
        yield return t;
    }
}

Then:

foreach (Dog dog in animal.AsOrEmpty<Dog>())
{
    // use dog
}

1 You can assign values in if statements, although I rarely do so. That's not the same as declaring variables though. It's not terribly unusual for me to do it in a while though when reading streams of data. For example:

string line;
while ((line = reader.ReadLine()) != null)
{
    ...
}

These days I normally prefer to use a wrapper which lets me use foreach (string line in ...) but I view the above as a pretty idiomatic pattern. It's usually not nice to have side-effects within a condition, but the alternatives usually involve code duplication, and when you know this pattern it's easy to get right.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 2
    One could write an `AsIf` extension (upon `object`) taking the destination type as a type paramater and an `Action` parameter for the body which encapsulates the `as` conversion and null check. (`IfIs` might be a more accurate though less entertaining name.) – Paul Ruane Aug 18 '11 at 20:06
  • 81
    +1 for giving an answer and also begging that the OP doesn't use it. Instant classic. – ckittel Aug 18 '11 at 20:06
  • @Paul: I was just doing that, as it happens. – Jon Skeet Aug 18 '11 at 20:10
  • 1
    Why do you need the `t = null` in the final `for` clause? It should work just fine without it. – jonp Aug 18 '11 at 20:11
  • 3
    @jonp If you wanted to loop forever, sure. – GManNickG Aug 18 '11 at 20:12
  • 1
    @jonp: It's not intended to be an actual loop; without that clause, it'd run forever. – Adam Robinson Aug 18 '11 at 20:13
  • 1
    @jonp: It's to stop the loop after one iteration. – Jon Skeet Aug 18 '11 at 20:14
  • I'm not sold on the (ab)use of `for` here though. – Paul Ruane Aug 18 '11 at 20:14
  • 9
    @Paul: If I were trying to *sell* it to anyone, I wouldn't strongly advise them not to use it. I'm just showing what's *possible*. – Jon Skeet Aug 18 '11 at 20:16
  • 12
    @Paul: I think that may have been the motivation behind `EVIL EVIL EVIL`, but I'm not positive. – Adam Robinson Aug 18 '11 at 20:16
  • @Adam Robinson: Evil is never positive, unless you're playing Dungeon Keeper, in which case it's **gooooood** – BoltClock Aug 18 '11 at 20:17
  • Inspiring. I didn't even suspect that C# can get so close to C++ in terms of being evil. – Kos Aug 18 '11 at 20:25
  • @Adam Robinson: I didn't notice the edits when I wrote the comment. It's all very fluxating. – Paul Ruane Aug 18 '11 at 20:26
  • 18
    I made a similar extension method (with a bunch of overloads) a while ago and I called them `AsEither(...)`, I think it's a bit clearer than `AsIf(...)`, so I can write `myAnimal.AsEither(dog => dog.Woof(), cat => cat.Meeow(), unicorn => unicorn.ShitRainbows())`. – herzmeister Aug 18 '11 at 21:37
  • 101
    That's the best abuse of C# I've seen in a while. Clearly you are an evil genius. – Eric Lippert Aug 18 '11 at 21:47
  • @herzmeister Nice API. Could save a lot of boilerplate if you find yourself in a switch() very often! – Greg B Aug 18 '11 at 21:47
  • 4
    @Eric: I feel deeply honoured - I can only imagine the horrific things the team must come up with at times :) – Jon Skeet Aug 18 '11 at 21:52
  • 7
    @Jon: The scary thing about this one is that I can actually imagine someone using it in production code. – Eric Lippert Aug 18 '11 at 22:49
  • 4
    +1 Jon for AsIf; from the unofficial fan club! of course, now I'm going to have to rewrite some of my production code with a Wayne's World variable naming scheme, just so I can figure out a way to use: `schyeah.AsIf` :D (wow, @Eric and Jon, both of you at the same time! [hmm.] anyway, just don't get too close to each other—don't want your reps to go prompt critical! :P) `isExcellent = xpert[0].AsIf(j => j.partyOn) && xpert[1].AsIf(e => e.partyOn());` :D ♡ – shelleybutterfly Aug 19 '11 at 00:45
  • 2
    @Jons - It goes to say you could use `Dog dog; if ((dog = animal as Dog) != null) { ... }`. You still need two lines to do that though; so you might as well go for clarity. – Jonathan Dickinson Aug 19 '11 at 14:01
  • @Jonathan: That still introduces the variable into the parent scope, which is what I believe the OP is trying to avoid. There's always "start a new block then" - but I think most of the time it's just fine to use the code at the top of my answer. – Jon Skeet Aug 19 '11 at 14:11
  • That kind of 0 or 1 collection looks very much like an Option pattern and is heavily (ab)used in Scala. – Grozz Sep 19 '11 at 15:20
  • +1 lol. I want to know when this hits production. What have you done!? – sgtz Sep 27 '11 at 10:29
  • Jon's `AsOrEmpty` is very nearly built-in: `foreach (var dog in new[] { animal }.OfType())` - nothing evil about that :) – Daniel Earwicker Oct 04 '11 at 13:44
  • 1
    AsOrEmpty sounds like the OfType<>() extension method of System.Linq – Ivo Feb 15 '12 at 18:11
  • @JonSkeet I would any day prefer a bool return type for `AsIf` :) – nawfal May 19 '13 at 22:38
  • I've used things like that for loop snippet... in C macros. That's where all the hacks come out in my experience. – Alexis King Sep 20 '13 at 04:32
  • @JoshKelley: Indeed - will add a little note. – Jon Skeet Jul 15 '17 at 07:18
49

If as fails, it returns null.

Dog dog = animal as Dog;

if (dog != null)
{
    // do stuff
}
Platinum Azure
  • 45,269
  • 12
  • 110
  • 134
  • First, thank you. Second, I want to create the dog variable in the scope of the `if` statement and not in outer scope. – michael Aug 18 '11 at 20:10
  • @Michael you cannot do that in an if statement. The if has to have a bool result not an assignment. Jon Skeet provides some nice generic and lambda combinations you may which to consider as well. – Rodney S. Foley Aug 18 '11 at 20:13
  • `if` can have a bool result *and* an assignment. `Dog dog; if ((dog = animal as Dog) != null) { // Use Dog }` but that still introduces the variable in the outer scope. – Tom Mayfield Aug 24 '11 at 17:36
13

You can assign the value to the variable, as long as the variable already exists. You can also scope the variable to allow that variable name to be used again later in the same method, if that is a problem.

public void Test()
{
    var animals = new Animal[] { new Dog(), new Duck() };

    foreach (var animal in animals)
    {
        {   // <-- scopes the existence of critter to this block
            Dog critter;
            if (null != (critter = animal as Dog))
            {
                critter.Name = "Scopey";
                // ...
            }
        }

        {
            Duck critter;
            if (null != (critter = animal as Duck))
            {
                critter.Fly();
                // ...
            }
        }
    }
}

assuming

public class Animal
{
}

public class Dog : Animal
{
    private string _name;
    public string Name
    {
        get { return _name; }
        set
        {
            _name = value;
            Console.WriteLine("Name is now " + _name);
        }
    }
}

public class Duck : Animal
{
    public void Fly()
    {
        Console.WriteLine("Flying");
    }
}

gets output:

Name is now Scopey
Flying

The pattern of variable assignment in the test is also used when reading byte blocks from streams, for example:

int bytesRead = 0;
while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0) 
{
    // ...
}

The pattern of variable scoping used above, however, is not a particularly common code pattern and if I saw it being used all over the place I'd be looking for a way to refactor it out.

Handcraftsman
  • 6,863
  • 2
  • 40
  • 33
12

Is there some syntax that allows me to write something like:

if (Dog dog = animal as Dog) { ... dog ... }

?

There likely will be in C# 6.0. This feature is called "declaration expressions". See

https://roslyn.codeplex.com/discussions/565640

for details.

The proposed syntax is:

if ((var i = o as int?) != null) { … i … }
else if ((var s = o as string) != null) { … s … }
else if ...

More generally, the proposed feature is that a local variable declaration may be used as an expression. This if syntax is just a nice consequence of the more general feature.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 1
    At a glance this seems less readable then just declaring the variable like you would today. Would you happen to know why this particular feature has managed to pass the -100 point bar? – asawyer Sep 16 '14 at 16:02
  • 3
    @asawyer: First, this is a very frequently requested feature. Second, other languages have this extension to "if"; gcc for example allows the equivalent in C++. Third, the feature is more general than just "if", as I noted. Fourth, there is a trend in C# since C# 3.0 to make more and more things that required a statement context instead require an expression context; this helps with functional-style programming. See the language design notes for more details. – Eric Lippert Sep 16 '14 at 16:20
  • 2
    @asawyer: You're welcome! Feel free to participate in the discussion on Roslyn.codeplex.com if you have more comments. Also, I would add: Fifth, the new Roslyn infrastructure lowers the marginal costs to the implementation team of doing these sorts of small experimental features, which means that the magnitude of the "minus 100" points is decreased. The team is taking this opportunity to explore perfectly decent small features that have been long-requested but never made it above the -100 point barrier previously. – Eric Lippert Sep 16 '14 at 17:45
  • 2
    Readers of these comments who are confused about what "points" we're talking about should read former C# designer Eric Gunnerson's blog post on this topic: http://blogs.msdn.com/b/ericgu/archive/2004/01/12/57985.aspx. This is an analogy; there are no actual "points" being counted up. – Eric Lippert Sep 16 '14 at 17:46
  • @asawyer: I think this feature really shines in calls to `Try*` (e.g., `TryParse`). This feature not only makes such calls into a single expression (as they should be, IMO), but also allows for cleaner scoping of such variables. I am enthusiastic about having the `out` parameter of a `Try` method be scoped to its conditional; this makes it harder to introduce certain types of bugs. – Brian Sep 16 '14 at 18:55
  • 1
    NOTE: it's 2020 (C# 9) and it's not possible to do `if ((var myLocal = myObj.myProp) != null ) { … myLocal … }` – Grzegorz Dev Nov 03 '20 at 15:10
9

One of the extension methods I find myself writing and using often* is

public static TResult IfNotNull<T,TResult>(this T obj, Func<T,TResult> func)
{
    if(obj != null)
    {
        return func(obj);
    }
    return default(TResult);
}

Which could be used in this situation as

string name = (animal as Dog).IfNotNull(x => x.Name);

And then name is the dog's name (if it is a dog), otherwise null.

*I have no idea if this is performant. It has never come up as a bottleneck in profiling.

Greg
  • 2,163
  • 1
  • 18
  • 40
5

Going against the grain here, but maybe you're doing it wrong in the first place. Checking for an object's type is almost always a code smell. Don't all Animals, in your example, have a Name? Then just call Animal.name, without checking whether it's a dog or not.

Alternatively, invert the method so that you call a method on Animal that does something differently depending on the concrete type of the Animal. See also: Polymorphism.

cthulhu
  • 3,142
  • 2
  • 23
  • 32
4

With C# 9.0 and .NET 5.0 you can write it using as like that:

Animal animal;
if (animal as Dog is not null and Dog dog)
{
    //You can get here only if animal is of type Dog and you can use dog variable only
    //in this scope
}

It is because the animal as Dog in if statement produces the same result as:

animal is Dog ? (Dog)(animal) : (Dog)null

then is not null part checks if the result of above statement isn't null. Only if this statement is true it creates variable of type Dog dog, which can't be null.

This feature came to C# 9.0 with Pattern Combinators, you can read more about that right here: https://learn.microsoft.com/pl-pl/dotnet/csharp/language-reference/proposals/csharp-9.0/patterns3#pattern-combinators

P.Pasterny
  • 41
  • 1
4

Shorter Statement

var dog = animal as Dog
if(dog != null) dog.Name ...;
Jonathan Sterling
  • 18,320
  • 12
  • 67
  • 79
jmogera
  • 1,689
  • 3
  • 30
  • 65
4

The problem (with the syntax) is not with the assignment, as the assignment operator in C# is a valid expression. Rather, it is with the desired declaration as declarations are statements.

If I must write code like that I will sometimes (depending upon the larger context) write the code like this:

Dog dog;
if ((dog = animal as Dog) != null) {
    // use dog
}

There are merits with the above syntax (which is close to the requested syntax) because:

  1. Using dog outside the if will result in a compile error as it is not assigned a value elsewhere. (That is, don't assign dog elsewhere.)
  2. This approach can also be expanded nicely to if/else if/... (There are only as many as as required to select an appropriate branch; this the big case where I write it in this form when I must.)
  3. Avoids duplication of is/as. (But also done with Dog dog = ... form.)
  4. Is no different than "idiomatic while". (Just don't get carried away: keep the conditional in a consistent form and and simple.)

To truly isolate dog from the rest of the world a new block can be used:

{
  Dog dog = ...; // or assign in `if` as per above
}
Bite(dog); // oops! can't access dog from above

Happy coding.

  • Point #1 that you offer is the first thing that came to my mind. Declare the variable but only assign in the if. The variable then can't be referenced from outside the if without a compiler error - perfect! – Ian Yates Oct 01 '13 at 10:57
3

Here's some additional dirty code (not as dirty as Jon's, though :-)) dependent on modifying the base class. I think it captures the intent while perhaps missing the point:

class Animal
{
    public Animal() { Name = "animal";  }
    public List<Animal> IfIs<T>()
    {
        if(this is T)
            return new List<Animal>{this};
        else
            return new List<Animal>();
    }
    public string Name;
}

class Dog : Animal
{
    public Dog() { Name = "dog";  }
    public string Bark { get { return "ruff"; } }
}


class Program
{
    static void Main(string[] args)
    {
        var animal = new Animal();

        foreach(Dog dog in animal.IfIs<Dog>())
        {
            Console.WriteLine(dog.Name);
            Console.WriteLine(dog.Bark);
        }
        Console.ReadLine();
    }
}
James Ashley
  • 539
  • 4
  • 4
3

If you have to do multiple such as-ifs one after one (and using polymorphism is not an option), consider using a SwitchOnType construct.

Community
  • 1
  • 1
Omer Raviv
  • 11,409
  • 5
  • 43
  • 82
2

Another late entry:

if (animal is Dog dog) 
{ 
    dog.Name="Fido"; 
}
else if (animal is Cat cat)
{
    cat.Name="Bast";
}
Valid
  • 767
  • 7
  • 14
  • I ran into this syntax in some code this morning and was unfamiliar with it. On one PC it looks OK. On another, the declared variable, like "dog" or "cat" in your example, has a squiggly red line and the code does not compile.... Can you point to where the C# documentation covers this feature? – GTAE86 Oct 10 '22 at 15:00
1

IDK if this helps anybody but you can always try to use a TryParse to assign your variable. Here is an example:

if (int.TryParse(Add(Value1, Value2).ToString(), out total))
        {
            Console.WriteLine("I was able to parse your value to: " + total);
        } else
        {
            Console.WriteLine("Couldn't Parse Value");
        }


        Console.ReadLine();
    }

    static int Add(int value1, int value2)
    {
        return value1 + value2;
    }

The total variable would be declared before your if statement.

1

you can use something like that

//Declare variable bool temp= false;

 if (previousRows.Count > 0 || (temp= GetAnyThing()))
                                    {
                                    }
1

With C# 7's Pattern Matching you can now do things like:

if (returnsString() is string msg) {
  Console.WriteLine(msg);
}

This question was asked over 10 years ago now so almost all the other answers are outdated/wrong

Dr-Bracket
  • 4,299
  • 3
  • 20
  • 28
0

Another EVIL solution with extension methods :)

public class Tester
{
    public static void Test()
    {
        Animal a = new Animal();

        //nothing is printed
        foreach (Dog d in a.Each<Dog>())
        {
            Console.WriteLine(d.Name);
        }

        Dog dd = new Dog();

        //dog ID is printed
        foreach (Dog dog in dd.Each<Dog>())
        {
            Console.WriteLine(dog.ID);
        }
    }
}

public class Animal
{
    public Animal()
    {
        Console.WriteLine("Animal constructued:" + this.ID);
    }

    private string _id { get; set; }

    public string ID { get { return _id ?? (_id = Guid.NewGuid().ToString());} }

    public bool IsAlive { get; set; }
}

public class Dog : Animal 
{
    public Dog() : base() { }

    public string Name { get; set; }
}

public static class ObjectExtensions
{
    public static IEnumerable<T> Each<T>(this object Source)
        where T : class
    {
        T t = Source as T;

        if (t == null)
            yield break;

        yield return t;
    }
}

I personally prefer the clean way:

Dog dog = animal as Dog;

if (dog != null)
{
    // do stuff
}
Stefan Michev
  • 4,795
  • 3
  • 35
  • 30
0

An if statement won't allow that, but a for loop will.

e.g.

for (Dog dog = animal as Dog; dog != null; dog = null)
{
    dog.Name;    
    ... 
}

In case the way it works is not immediately obvious then here is a step by step explanation of the process:

  • Variable dog is created as type dog and assigned the variable animal that is cast to Dog.
  • If the assignment fails then dog is null, which prevents the contents of the for loop from running, because it is immediately broken out of.
  • If the assignment succeeds then the for loop runs through the
    iteration.
  • At the end of the iteration, the dog variable is assigned a value of null, which breaks out of the for loop.
WonderWorker
  • 8,539
  • 4
  • 63
  • 74
0
using(Dog dog = animal as Dog)
{
    if(dog != null)
    {
        dog.Name;    
        ... 

    }

}
WonderWorker
  • 8,539
  • 4
  • 63
  • 74
0

I know I'm super duper late to the party, but I figured I'd post my own workaround to this dilemma since I haven't seen it on here yet (or anywhere for that matter).

/// <summary>
/// IAble exists solely to give ALL other Interfaces that inherit IAble the TryAs() extension method
/// </summary>
public interface IAble { }

public static class IAbleExtension
{
    /// <summary>
    /// Attempt to cast as T returning true and out-ing the cast if successful, otherwise returning false and out-ing null
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="able"></param>
    /// <param name="result"></param>
    /// <returns></returns>
    public static bool TryAs<T>(this IAble able, out T result) where T : class
    {
        if (able is T)
        {
            result = able as T;
            return true;
        }
        else
        {
            result = null;
            return false;
        }
    }

    /// <summary>
    /// Attempt to cast as T returning true and out-ing the cast if successful, otherwise returning false and out-ing null
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="obj"></param>
    /// <param name="result"></param>
    /// <returns></returns>
    public static bool TryAs<T>(this UnityEngine.Object obj, out T result) where T : class
    {
        if (obj is T)
        {
            result = obj as T;
            return true;
        }
        else
        {
            result = null;
            return false;
        }
    }
}

With this, you can do things such as:

if (animal.TryAs(out Dog dog))
{
    //Do Dog stuff here because animal is a Dog
}
else
{
    //Cast failed! animal is not a dog
}

IMPORTANT NOTE: If you want to use TryAs() using an Interface, you MUST have that interface inheriting IAble.

Enjoy!

0

Al little experimentation shows that we can use assignment in an if statement

public static async Task Main(string[] args)
{
    bool f = false;
    if (f = Tru())
    {
        System.Diagnostics.Debug.WriteLine("True");
    }
    if (f = Tru(false))
    {
        System.Diagnostics.Debug.WriteLine("False");
    }
}

private static bool Tru(bool t = true) => t ? true : false;

enter image description here

As far as any potential side effects or "Evil", I can't think of any, although I am sure somebody can. Comments welcome!

mattylantz
  • 312
  • 4
  • 10
0

There is workaround, I give you a little bit another example, you have a method which returns string Id, than if statement:

var userId = GetUserId();

if (!string.IsNullOrEmpty(userId))
{ 
    //logic
}

you might be expected syntax like this, which isn't work:

if (string userId = GetUserId() && !string.IsNullOrEmpty(userId))
{
    //logic
}

But now, you can achieve the same result with:

if (GetUserId() is string userId && !string.IsNullOrEmpty(userId))
{ 
    //logic
}

In your example you can do of course:

if(animal is Dog dog)
{
    //logic
}

but it find be usefull to consider using a method

var animal = GetAnimal();

if (animal is Dog)
{
    //logic
}

and finally you can rewrite it to:

if(GetAnimal() is Dog dog)
{
    //logic
}
ElConrado
  • 1,477
  • 4
  • 20
  • 46