if 문의 할당


142

클래스 Animal와 하위 클래스가 Dog있습니다. 나는 종종 다음 줄을 코딩하는 것을 발견했다.

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

변수의 경우 Animal animal;.

다음과 같은 것을 작성할 수있는 구문이 있습니까?

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

1
그게 무슨 뜻일까요? 무엇을 할 bool조건이 될?
Kirk Woll

내가 아는 것은 없습니다. 이름을 동물로 옮기지 않는 이유는 무엇입니까?
AlG

22
참고로, 코드와 같은 코드는 종종 SOLID Principles 중 하나를 위반 한 결과 일 수 있습니다 . L -리스 코프 치환 원칙 . 항상하고있는 일을하는 것은 잘못된 일이지만 생각할 가치가있을 수 있습니다.
ckittel

@ckittel이하고있는 일을 기록해 두십시오. 아마도 이것을하고 싶지 않을 것입니다.
khebbie

1
C #에서 @Solo no,! null= false; C #은 실제 부울 또는 if조건 에서 암시 적으로 부울로 변환 할 수있는 것만 허용 합니다. 널 (null)이나 정수 유형은 암시 적으로 bool로 변환 할 수 없습니다.
로마 Starkov

답변:


323

아래 답변은 몇 년 전에 작성되었으며 시간이 지남에 따라 업데이트되었습니다. C # 7부터 패턴 일치를 사용할 수 있습니다.

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

주의 dog애프터 범위에 여전히 if문,하지만 확실히 할당되지 않습니다.


아닙니다. 이것을 작성하는 것이 관용적입니다.

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

"다음에 if"가 거의 항상 이런 식으로 사용 된다는 것을 감안할 때 , 한 번에 두 부분을 모두 수행하는 연산자가있는 것이 더 합리적 일 수 있습니다. 패턴 일치 제안 이 구현 된 경우 현재 C # 6에는 없지만 C # 7에 포함될 수 있습니다 .

문제는 명령문 1 의 조건 부분에서 변수를 선언 할 수 없다는 것 입니다. 내가 생각할 수있는 가장 가까운 접근법은 다음과 같습니다.if

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

그것은 단지 불쾌합니다 ... (방금 시도했지만 작동합니다. 그러나 제발, 제발, 제발하지 마십시오. 아, 물론 dog사용하여 선언 할 수 있습니다 var.)

물론 확장 방법을 작성할 수 있습니다.

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

그런 다음 전화하십시오.

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

또는 두 가지를 결합 할 수 있습니다.

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);
    }
}

for 루프보다 깔끔한 방식으로 람다식이없는 확장 메서드를 사용할 수도 있습니다.

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

그때:

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

1 거의 사용하지 않지만 명령문에 값을 지정할 수 있습니다 if. 그러나 변수 선언과는 다릅니다. 데이터 스트림을 읽을 때 가끔 그렇게하는 것은 끔찍한 일 이 아닙니다 while. 예를 들면 다음과 같습니다.

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

요즘에는 일반적으로 래퍼를 사용하여 사용할 수 foreach (string line in ...)있지만 위의 내용을 꽤 관용적 인 패턴으로 생각합니다. 그것은이다 일반적으로 조건 내에서 부작용을 가지고 좋은 아니지만, 대안은 일반적으로 코드 중복을 포함, 당신은이 패턴을 알고있을 때 그것을 바로 얻을 쉽습니다.


76
답변을 제공하고 OP에서 사용하지 않기를 간청 한 +1 인스턴트 클래식.
ckittel

8
@Paul : 다른 사람 에게 팔려고 한다면 사용하지 말 것을 강력히 권하지 않습니다. 나는 단지 가능한 것을 보여주고 있습니다 .
Jon Skeet

12
@Paul : 나는 그것이 동기가 된 것으로 생각 EVIL EVIL EVIL하지만 긍정적이지 않습니다.
Adam Robinson

18
나는 비슷한 확장 방법 (많은 과부하로)을 만들었고 그것을 호출했는데 AsEither(...), 나는 그것이 조금 더 명확하다고 생각 AsIf(...)하므로 쓸 수있다 myAnimal.AsEither(dog => dog.Woof(), cat => cat.Meeow(), unicorn => unicorn.ShitRainbows()).
herzmeister

97
그것은 내가 본 C #의 최고의 남용입니다. 분명히 당신은 사악한 천재입니다.
Eric Lippert

48

경우 as에 실패, 그것은 반환합니다 null.

Dog dog = animal as Dog;

if (dog != null)
{
    // do stuff
}

먼저 감사합니다. 둘째, if외부 변수가 아닌 명령문 범위에서 dog 변수를 만들고 싶습니다 .
마이클

@Michael은 if 문에서 그렇게 할 수 없습니다. if는 과제가 아닌 부울 결과가 있어야합니다. Jon Skeet은 당신이 고려해야 할 몇 가지 좋은 일반적인 람다 조합을 제공합니다.
Rodney S. Foley

if부울 결과 과제를 가질 수 있습니다 . Dog dog; if ((dog = animal as Dog) != null) { // Use Dog }그러나 여전히 외부 범위에 변수가 도입됩니다.
Tom Mayfield

12

변수가 이미 존재하는 한 변수에 값을 지정할 수 있습니다 . 문제가있는 경우 나중에 같은 방법으로 해당 변수 이름을 다시 사용할 수 있도록 변수의 범위를 지정할 수도 있습니다.

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();
                // ...
            }
        }
    }
}

가정

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");
    }
}

출력을 얻는다 :

Name is now Scopey
Flying

테스트에서 변수 할당 패턴은 또한 스트림에서 바이트 블록을 읽을 때 사용됩니다.

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

그러나 위에서 사용 된 변수 범위 패턴은 특히 일반적인 코드 패턴이 아니며, 모든 곳에서 사용되는 것을 보았을 때 리팩토링 할 방법을 찾고있었습니다.


11

다음과 같은 것을 작성할 수있는 구문이 있습니까?

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

?

C # 6.0에있을 것입니다. 이 기능을 "선언 표현식"이라고합니다. 보다

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

자세한 내용은.

제안 된 구문은 다음과 같습니다.

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

보다 일반적으로, 제안 된 특징은 로컬 변수 선언이 표현식으로 사용될 수 있다는 것 입니다. 이 if구문은 더 일반적인 기능의 좋은 결과입니다.


1
한 눈에 이것은 읽기가 쉽지 않고 오늘날처럼 변수를 선언하는 것입니다. 이 특정 기능이 왜 -100 포인트 바를 통과했는지 알고 싶습니까?
asawyer

3
@asawyer : 첫째, 이것은 매우 자주 요청되는 기능입니다. 둘째로, 다른 언어는이 확장명을 "if"로 갖습니다. 예를 들어 gcc는 C ++에서 동등한 것을 허용합니다. 셋째,이 기능은 내가 언급 한 것처럼 "if"보다 더 일반적입니다. 넷째, C # 3.0 이후로 C #에는 명령문 컨텍스트가 필요한 표현식 컨텍스트가 점점 더 필요한 경향이 있습니다. 이것은 함수형 프로그래밍에 도움이됩니다. 자세한 내용은 언어 디자인 노트를 참조하십시오.
Eric Lippert

2
@asawyer : 천만에요! 의견이 더 있으면 Roslyn.codeplex.com에 대한 토론에 자유롭게 참여하십시오. 다섯째, 새로운 Roslyn 인프라는 이러한 종류의 작은 실험 기능을 수행하는 구현 팀의 한계 비용을 낮추므로 "마이너스 100"포인트의 크기가 줄어 듭니다. 팀은이 기회를 통해 오랫동안 요청되었지만 완벽하게 이전에 -100 포인트를 초과 한 적이없는 작은 기능을 탐색하고 있습니다.
Eric Lippert

1
우리가 말하는 "포인트"에 대해 혼란스러워하는이 의견을 읽는 독자는이 주제에 대한 전 C # 디자이너 Eric Gunnerson의 블로그 게시물을 읽어야합니다. blogs.msdn.com/b/ericgu/archive/2004/01/12/57985. aspx . 이것은 비유입니다. 카운트되는 실제 "포인트"가 없습니다.
Eric Lippert

@asawyer :이 기능은 Try*(예 :)를 호출 할 때 실제로 빛난다 고 생각합니다 TryParse. 이 기능을 사용하면 이러한 호출을 IMO처럼 단일 표현식으로 만들뿐만 아니라 이러한 변수를보다 명확하게 범위 지정할 수 있습니다. 나는 메소드 의 out매개 변수가 Try조건부로 범위가 정해 지는 것에 열성적입니다 . 이로 인해 특정 유형의 버그를 도입하기가 어렵습니다.
브라이언

9

내가 자주 쓰고 사용하는 확장 방법 중 하나는 *

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

이 상황에서 사용할 수있는 것은

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

그리고 개의 name이름입니다 (개인 경우). 그렇지 않으면 null입니다.

* 이것이 성능 적인지 모르겠습니다. 프로파일 링에서 병목 현상이 발생하지 않았습니다.


2
메모 +1 프로파일 링에서 병목 현상이 발생하지 않았다면 이는 성능 이 충분 하다는 꽤 좋은 신호입니다 .
코디 그레이

왜 defaultValue를 인수로 사용하여 호출자가 기본값 (....)으로 돌아 가지 않고 I z를 결정하게합니까?
Trident D' Gao

5

여기서 곡식에 반대하지만 처음에 잘못하고있을 수 있습니다. 객체의 유형을 확인하는 것은 거의 항상 코드 냄새입니다. 귀하의 예에서 모든 동물이 이름을 가지고 있지는 않습니까? 그런 다음 개인 지 여부를 확인하지 않고 Animal.name을 호출하십시오.

또는 Animal의 구체적인 유형에 따라 다르게 작동하는 Animal 메서드를 호출하도록 메서드를 반전시킵니다. 참조 : 다형성.


4

짧은 진술

var dog = animal as Dog
if(dog != null) dog.Name ...;

3

다음은 기본 클래스 수정에 의존하는 더티 코드 (Jon의 것보다 더럽지 않은)입니다. 나는 그것이 요점을 놓치면서 의도를 포착한다고 생각합니다.

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();
    }
}


3

C #의 대입 연산자가 유효한 식이므로 문제 (구문 사용)가 대입과 관련 이 없습니다 . 오히려 선언이 진술이므로 원하는 선언이 있습니다.

그런 코드를 작성 해야하는 경우 때로는 더 큰 컨텍스트에 따라 다음과 같이 코드를 작성합니다.

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

위의 구문 (요청 된 구문에 가까운)에는 다음과 같은 장점이 있습니다.

  1. dog 외부 에서 사용하면 if다른 곳에 값이 할당되지 않으므로 컴파일 오류가 발생합니다. (즉, 다른 곳에 할당 하지 마십시오dog .)
  2. 이러한 접근 방식은 또한에 잘 확장 할 수 있습니다 if/else if/...(만큼만 있습니다 as, 적절한 지점을 선택하기 위해 필요에 따라 이 큰 경우 . 내가해야 내가이 양식을 작성)
  3. 의 중복을 피하십시오 is/as. (그러나 Dog dog = ...양식으로 도 수행됩니다 .)
  4. "아이디 오마 틱 동안"과 다르지 않습니다. (단지 수행하지 마십시오. 조건을 일관된 형태로 단순하게 유지하십시오.)

dog다른 세계와 진정으로 분리하기 위해 새로운 블록을 사용할 수 있습니다.

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

행복한 코딩.


당신이 제공하는 포인트 # 1은 내 마음에 온 첫 번째 것입니다. 변수를 선언하고 if에만 할당하십시오. 그런 다음 컴파일러 오류없이 if 외부에서 변수를 참조 할 수 없습니다-완벽합니다!
이안 예이츠

1

당신은 그런 것을 사용할 수 있습니다

// 변수 선언 bool temp = false;

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

0

확장 방법을 가진 또 다른 EVIL 솔루션 :)

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;
    }
}

나는 개인적으로 깨끗한 방법을 선호합니다 :

Dog dog = animal as Dog;

if (dog != null)
{
    // do stuff
}

0

if 문은 허용하지 않지만 for 루프는 허용합니다.

예 :

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

작동 방식이 즉시 명확하지 않은 경우 프로세스에 대한 단계별 설명이 있습니다.

  • 가변 개는 유형 개로 작성되며 Dog에 캐스트되는 가변 동물이 지정됩니다.
  • 할당이 실패하면 dog가 널 (null)이므로 for 루프의 내용이 즉시 중단되므로 for 루프의 내용이 실행되지 않습니다.
  • 할당이 성공하면 for 루프는
    반복을 통해 실행됩니다 .
  • 반복이 끝나면 dog 변수에 null 값이 할당되어 for 루프에서 빠져 나옵니다.


0

IDK가 도움이된다면 언제든지 TryParse를 사용하여 변수를 할당하려고 시도 할 수 있습니다. 예를 들면 다음과 같습니다.

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;
    }

변수는 경우 문 앞에 선언 될 것이다.


0

방금 if 문을 인라인하여 관심있는 것처럼 보이는 코드 줄을 만들었습니다. 코드를 압축하는 데 도움이되며 할당을 중첩 할 때 특히 더 읽기 쉽습니다.

var dog = animal as Dog; if (dog != null)
{
    Console.WriteLine("Parent Dog Name = " + dog.name);

    var purebred = dog.Puppy as Purebred; if (purebred != null)
    {
         Console.WriteLine("Purebred Puppy Name = " + purebred.Name);
    }

    var mutt = dog.Puppy as Mongrel; if (mutt != null)
    {
         Console.WriteLine("Mongrel Puppy Name = " + mutt.Name);
    }
 }

0

나는 파티에 늦게까지 열심히 일한다는 것을 알고 있지만, 나는이 딜레마에 아직 내 자신의 해결책을 게시 할 것이라고 생각했다.

/// <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;
        }
    }
}

이를 통해 다음과 같은 작업을 수행 할 수 있습니다.

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

중요 참고 : 인터페이스를 사용하여 TryAs ()를 사용하려면 해당 인터페이스가 IAble을 상속해야합니다.

즐겨! 🙂

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.