IEnumerable에 ForEach 확장 방법이없는 이유는 무엇입니까?


389

실종에 대해 묻는 또 다른 질문에서 영감을 얻었습니다. Zip 기능 .

수업에 ForEach확장 방법 이없는 이유는 무엇 Enumerable입니까? 아니면 어디서? ForEach메소드 를 얻는 유일한 클래스 는 List<>입니다. 누락 된 이유 (성능)가 있습니까?


foreach는 C # 및 VB.NET에서 언어 키워드로 지원된다는 점에서 아마도 이전 게시물에서 암시했을 것입니다.
chrisb

2
'공식 답변'에 대한 링크가있는 관련 질문 stackoverflow.com/questions/858978/…
Benjol


매우 중요한 점 중 하나는 foreach 루프가 더 찾기 쉽다는 것입니다. .ForEach (...)를 사용하면 코드를 살펴볼 때 쉽게 놓칠 수 있습니다. 성능 문제가있을 때 중요합니다. 물론 .ToList ()와 .ToArray ()는 같은 문제가 있지만 조금 다르게 사용됩니다. 또 다른 이유는 .ForEach에서 더 이상 "순수한"쿼리가되지 않도록 소스 목록 (요소 제거 / 추가)을 수정하는 것이 더 일반적이기 때문일 수 있습니다.
Daniel Bişar

병렬 코드를 디버깅 할 때 종종 후자가 존재하지 않는 것을 다시 발견하기 Parallel.ForEach위해 스와핑 을 시도 Enumerable.ForEach합니다. C #은 여기서 쉽게 할 수있는 트릭을 놓쳤습니다.
대령 패닉

답변:


198

대부분의 작업을 수행하는 언어에는 이미 foreach 문이 포함되어 있습니다.

나는 다음을 보는 것을 싫어한다.

list.ForEach( item =>
{
    item.DoSomething();
} );

대신에:

foreach(Item item in list)
{
     item.DoSomething();
}

후자는 입력하는 데 약간 더 길지만 대부분의 상황에서 더 명확하고 읽기 쉽습니다 .

그러나 그 문제에 대한 입장을 바꿨 음을 인정해야합니다. ForEach () 확장 메소드는 실제로 어떤 상황에서는 유용 할 것입니다.

명령문과 메소드의 주요 차이점은 다음과 같습니다.

  • 유형 검사 : foreach는 런타임에 수행되고 ForEach ()는 컴파일 시간에 수행됩니다 (Big Plus!).
  • 델리게이트를 호출하는 구문은 실제로 훨씬 간단합니다. objects.ForEach (DoSomething);
  • ForEach ()는 연결될 수 있습니다 : 그러한 기능의 악 / 유용성은 토론에 열려 있습니다.

이것들은 모두 많은 사람들에 의해 만들어졌으며 사람들이 왜 기능이 빠져 있는지 볼 수 있습니다. Microsoft가 다음 프레임 워크 반복에서 표준 ForEach 메서드를 추가해도 상관 없습니다.


39
여기서 논의 할 내용은 다음 같습니다. forums.microsoft.com/MSDN/… 기본적으로 확장 방법을 기능적으로 "순결"하게 유지하기로 결정했습니다. ForEach는 의도하지 않은 확장 방법을 사용할 때 부작용을 권장합니다.
mancaus

17
C 배경이 있다면 'foreach'가 더 분명해 보일 수 있지만 내부 반복은 의도를 더 명확하게 나타냅니다. 즉, 'sequence.AsParallel (). ForEach (...)'와 같은 작업을 수행 할 수 있습니다.
Jay Bazuzi

10
유형 검사에 대한 귀하의 요점이 정확하지 않습니다. foreach열거 가능 매개 변수가있는 경우 컴파일 타임에 유형 검사입니다. 가상의 ForEach확장 방법 과 마찬가지로 제네릭이 아닌 컬렉션에 대한 컴파일 시간 유형 검사 만 부족 합니다.
Richard Poole

49
확장 방법은 다음과 같이 점 표기법이있는 체인 LINQ에서 실제로 유용합니다 col.Where(...).OrderBy(...).ForEach(...). foreach ()에서 중복 로컬 변수 또는 긴 괄호로 화면이 오염되지 않는 훨씬 간단한 구문.
Jacek Gorgoń

22
"다음 내용을보고 싶지 않습니다."-개인 취향에 관한 질문이 아니 었으며, 외부 줄 바꿈과 불필요한 중괄호 쌍을 추가하여 코드를 더 증오하게 만들었습니다. 너무 증오하지 않습니다 list.ForEach(item => item.DoSomething()). 그리고 누가 목록이라고합니까? 명백한 사용 사례는 체인의 끝입니다. foreach 문을 사용하면 전체 체인을 뒤에 넣고 in블록 내에서 수행 해야하는 작업을 수행해야합니다.
Jim Balter

73

ForEach 방법은 LINQ 전에 추가되었습니다. ForEach 확장을 추가하면 확장 메서드 제약으로 인해 List 인스턴스에 대해 호출되지 않습니다. 나는 그것이 추가되지 않은 이유는 기존의 것과 간섭하지 않기 때문이라고 생각합니다.

그러나이 멋진 기능을 실제로 놓치면 자신의 버전을 롤아웃 할 수 있습니다

public static void ForEach<T>(
    this IEnumerable<T> source,
    Action<T> action)
{
    foreach (T element in source) 
        action(element);
}

11
확장 방법으로 가능합니다. 주어진 메소드 이름의 인스턴스 구현이 이미있는 경우 확장 메소드 대신 해당 메소드가 사용됩니다. 따라서 확장 메서드 ForEach를 구현하고 List <>. ForEach 메서드와 충돌하지 않도록 할 수 있습니다.
Cameron MacFarland

3
Cameron, 확장 방법이 어떻게 작동하는지 알고 있습니다. :) List는 IEnumerable을 상속하므로 명확하지 않습니다.
aku

9
확장 메서드 프로그래밍 가이드 ( msdn.microsoft.com/en-us/library/bb383977.aspx )에서 : 인터페이스 또는 클래스 메서드와 이름 및 서명이 같은 확장 메서드는 호출되지 않습니다. 나에게는 꽤 분명해 보인다.
Cameron MacFarland

3
내가 다른 말을하고 있습니까? 많은 클래스에 ForEach 메서드가 있으면 좋지 않지만 일부는 다르게 작동 할 수 있습니다.
aku

5
List <>. ForEach 및 Array.ForEach에 대해 한 가지 흥미로운 점은 실제로 foreach 구문을 내부적으로 사용하지 않는다는 것입니다. 둘 다 평문 for 루프를 사용합니다. 아마 성능 일 것입니다 ( diditwith.net/2006/10/05/PerformanceOfForeachVsListForEach.aspx ). 그러나 Array와 List 모두 ForEach 메서드를 구현한다는 점에서 IEnumerable <>이 아니라면 적어도 IList <>에 대한 확장 메서드를 구현하지 않은 것은 놀라운 일입니다.
Matt Dotson

53

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

// Possibly call this "Do"
IEnumerable<T> Apply<T> (this IEnumerable<T> source, Action<T> action)
{
    foreach (var e in source)
    {
        action(e);
        yield return e;
    }
}

찬성

체이닝 가능 :

MySequence
    .Apply(...)
    .Apply(...)
    .Apply(...);

단점

반복을 강제로 할 때까지 실제로는 아무것도하지 않습니다. 따라서 호출해서는 안됩니다 .ForEach(). 당신은 쓸 수 .ToList()끝, 또는 당신도,이 확장 방법을 쓸 수있다 :

// possibly call this "Realize"
IEnumerable<T> Done<T> (this IEnumerable<T> source)
{
    foreach (var e in source)
    {
        // do nothing
        ;
    }

    return source;
}

이는 배송 C # 라이브러리에서 너무 이탈 된 것일 수 있습니다. 확장 방법에 익숙하지 않은 독자는 코드로 무엇을 만들어야할지 모릅니다.


1
다음과 같이 작성하면 '실현'방법없이 첫 번째 함수를 사용할 수 있습니다.{foreach (var e in source) {action(e);} return source;}
jjnguy

3
Apply 메소드 대신 Select를 사용할 수 없습니까? 각 요소에 작업을 적용하고 생성하는 것이 Select 가하는 일과 정확히 같습니다. 예numbers.Select(n => n*2);
rasmusvhansen

1
이 목적으로 Select를 사용하는 것보다 Apply가 더 읽기 쉽다고 생각합니다. Select 문을 읽을 때 Apply가 "일부 작업"과 같이 "내부에서 가져 오기"로 읽습니다.
Dr Rob Lang

2
실제로 @rasmusvhansen Select()은 각 요소에 함수를 Apply()적용하고 Action각 요소에를 적용 하고 원래 요소를 반환하는 함수의 값을 반환 한다고 말합니다 .
NetMage

1
이 동등하다Select(e => { action(e); return e; })
짐 Balter

35

여기서 논의 하면 답이됩니다.

실제로, 내가 목격 한 구체적인 논의는 실제로 기능적 순도에 달려 있습니다. 표현에서, 부작용이 없다는 가정이 자주 있습니다. ForEach를 갖는 것은 부작용을 피하기보다는 구체적으로 초대합니다. -키이스 파머 (파트너)

기본적으로 확장 방법을 기능적으로 "순결"하게 유지하기로 결정했습니다. ForEach는 Enumerable 확장 메서드를 사용할 때 부작용이 없었지만 의도는 없었습니다.


6
blogs.msdn.com/b/ericlippert/archive/2009/05/18/… 도 참조하십시오 . Ddoing은 다른 모든 시퀀스 연산자가 기반으로하는 기능적 프로그래밍 원칙을 위반합니다.
분명히이

7
그 추론은 완전히 허위입니다. 사용 사례는 부작용이 필요하다고 ... foreach는하지 않고, 당신이 foreach 루프를 작성해야합니다. ForEach의 존재는 부작용을 권장하지 않으며 부작용이 없어도 부작용이 없습니다.
Jim Balter

확장 방법을 작성하는 것이 얼마나 간단한가를 고려할 때 말 그대로 언어 표준이 아닌 이유는 없습니다.
Prinny 선장

17

foreach대부분의 경우 내장 구문 을 사용하는 것이 낫다는 데 동의하지만 ForEach <> 확장 에서이 변형을 사용하면 색인을 정기적으로 관리 해야하는 것보다 조금 더 좋습니다 foreach.

public static int ForEach<T>(this IEnumerable<T> list, Action<int, T> action)
{
    if (action == null) throw new ArgumentNullException("action");

    var index = 0;

    foreach (var elem in list)
        action(index++, elem);

    return index;
}
var people = new[] { "Moe", "Curly", "Larry" };
people.ForEach((i, p) => Console.WriteLine("Person #{0} is {1}", i, p));

당신에게 줄 것입니다 :

Person #0 is Moe
Person #1 is Curly
Person #2 is Larry

확장 성이 뛰어나지 만 문서를 추가 할 수 있습니까? 반환 값의 의도 된 용도는 무엇입니까? 왜 특별히 int가 아닌 index var입니까? 샘플 사용법?
Mark T

Mark : 반환 값은 목록의 요소 수입니다. 암시 적 타이핑 덕분에 인덱스 특히 int로 입력됩니다.
Chris Zwiryk

@Chris, 위의 코드에 따라 반환 값은 목록의 요소 수가 아니라 목록의 마지막 값에 대한 0부터 시작하는 인덱스입니다.
Eric Smith

@Chris, no not : 값이 취해진 후 인덱스가 증가하고 (접두어 증가) 첫 번째 요소가 처리 된 후에 인덱스는 1이됩니다. 따라서 반환 값은 실제로 요소 수입니다.
MartinStettner

1
@MartinStettner 5/16에 대한 Eric Smith의 의견은 이전 버전에서 나온 것입니다. 그는 5/18의 코드를 수정하여 올바른 값을 반환했습니다.
Michael Blackburn

16

한 가지 해결 방법은 작성하는 것 .ToList().ForEach(x => ...)입니다.

찬성

이해하기 쉬움-독자는 추가 확장 방법이 아니라 C #과 함께 제공되는 내용 만 알면됩니다.

구문 노이즈는 매우 약합니다 (조금 코드 만 추가).

.ForEach()어쨌든 네이티브 가 전체 컬렉션을 실현해야하기 때문에 일반적으로 추가 메모리가 필요하지 않습니다 .

단점

작업 순서는 이상적이지 않습니다. 차라리 하나의 요소를 깨닫고 행동하고 반복합니다. 이 코드는 모든 요소를 ​​먼저 인식 한 다음 각 요소를 순서대로 작동합니다.

목록을 실현하면 예외가 발생하더라도 단일 요소에 대해 조치를 취하지 않아도됩니다.

열거 형이 자연수와 같이 무한하다면 운이 나쁘다.


1
a) 이것은 원격으로 질문에 대한 답변이 아닙니다. b)이 반응은 Enumerable.ForEach<T>방법 이 없지만 방법 이 있다고 미리 언급하면 ​​더 명확 해집니다 List<T>.ForEach. c) "네이티브 .ForEach ()는 어쨌든 전체 컬렉션을 구현해야하기 때문에 일반적으로 추가 메모리가 필요하지 않습니다." -완전하고 말도 안되는 말. 여기 Enumerable.ForEach <T>의 구현은했다면 C 번호를 제공 할 것입니다 :public static void ForEach<T>(this IEnumerable<T> list, Action<T> action) { foreach (T item in list) action(item); }
짐 Balter

13

나는 항상 나 자신을 궁금해했습니다. 그래서 나는 항상 이것을 가지고 다닙니다.

public static void ForEach<T>(this IEnumerable<T> col, Action<T> action)
{
    if (action == null)
    {
        throw new ArgumentNullException("action");
    }
    foreach (var item in col)
    {
        action(item);
    }
}

작은 확장 방법이 좋습니다.


2
col은 null 일 수 있습니다 ... 확인 할 수도 있습니다.
Amy B

그래, 나는 내 도서관에서 체크인했는데, 거기서 빨리 할 때 놓쳤다.
Aaron Powell

모두가 action 매개 변수가 null인지 확인하지만 source / col 매개 변수는 확인하지 않는 것이 흥미 롭습니다.
Cameron MacFarland

2
예외가 아닌 결과를 확인하지 않을 때 모두가 null에 대해 매개 변수를 확인하는 것이 흥미 롭습니다 ...
oɔɯǝɹ

전혀. Null Ref 예외는 결함이있는 매개 변수의 이름을 알려주지 않습니다. 또한 루프에서 체크인하여 col [index #]이 같은 이유로 null이라고 말하는 특정 Null Ref를 던질 수 있습니다.
Roger Willcocks

8

따라서 ForEach 확장 메서드는 LINQ 확장 메서드와 같은 값을 반환하지 않기 때문에 적절하지 않다는 사실에 대해 많은 의견이있었습니다. 이것은 사실적인 진술이지만 완전히 사실이 아닙니다.

LINQ 확장 메소드는 모두 서로 연결될 수 있도록 값을 리턴합니다.

collection.Where(i => i.Name = "hello").Select(i => i.FullName);

그러나 LINQ가 확장 메서드를 사용하여 구현되었다고해서 확장 메서드가 반드시 같은 방식으로 사용되어 값을 반환 . 값을 반환하지 않는 공통 기능을 노출시키기 위해 확장 메소드를 작성하는 것은 완벽하게 유효한 사용법입니다.

ForEach에 대한 구체적인 주장은 확장 메서드에 대한 제약 조건 (즉, 확장 메서드가 동일한 서명으로 상속 된 메서드를 재정의 하지 않을 것임 )에 따라 사용자 지정 확장 메서드가 중요하지 않은 모든 클래스에서 사용 가능한 상황이있을 수 있다는 것입니다 IEnumerable > 목록 제외 >. 확장 메소드 또는 상속 메소드의 호출 여부에 따라 메소드가 다르게 작동하기 시작할 때 혼동을 일으킬 수 있습니다.<T<T


7

(체인 가능하지만 게으른 평가)를 사용할 수 있습니다 Select 먼저 작업을 수행 한 다음 신원을 반환 할 수 있습니다 (또는 원하는 경우 다른 것)

IEnumerable<string> people = new List<string>(){"alica", "bob", "john", "pete"};
people.Select(p => { Console.WriteLine(p); return p; });

Count()(fafa를 열거하는 가장 저렴한 작업) 또는 어쨌든 필요한 다른 작업을 사용하여 평가되는지 확인 해야합니다.

그래도 표준 라이브러리로 가져 오는 것을보고 싶습니다.

static IEnumerable<T> WithLazySideEffect(this IEnumerable<T> src, Action<T> action) {
  return src.Select(i => { action(i); return i; } );
}

위의 코드 people.WithLazySideEffect(p => Console.WriteLine(p))는 foreach와 실질적으로 동일하지만 게으르고 체인 가능합니다.


환상적으로, 반환 된 선택이 이와 같이 작동한다고 클릭하지 않았습니다. 표현식 구문 으로이 작업을 수행 할 수 있다고 생각하지 않기 때문에 메소드 구문을 잘 사용합니다 (따라서 이전에는 생각하지 않았습니다).
Alex KeySmith

@AlexKeySmith C #에 이전과 같은 시퀀스 연산자가 있으면 식 구문으로 수행 할 수 있습니다. 그러나 ForEach의 요점은 void 유형으로 작업을 수행하며 C #의 표현식에 void 유형을 사용할 수 없다는 것입니다.
Jim Balter

imho, 이제는 Select더 이상해야 할 일을하지 않습니다. 그것은 OP가 요구하는 것에 대한 해결책입니다. 그러나 주제 가독성에 대해서는 선택이 기능을 실행할 것으로 기대하지 않습니다.
Matthias Burger

@MatthiasBurger Select해야 할 일을하지 Select않으면 호출하는 코드가 아니라 의 구현에 문제가 Select되는데, 이는 무엇에 영향을 미치지 Select않습니다.
Martijn


4

@Coincoin

foreach 확장 메서드의 진정한 힘은 Action<>불필요한 메서드를 코드에 추가하지 않고도 재사용 할 수 있다는 것입니다. 10 개의 목록이 있고 동일한 논리를 수행하려고하며 해당 기능이 클래스에 적합하지 않으며 재사용되지 않는다고 가정하십시오. 10 개의 for 루프 나 속하지 않는 도우미 인 일반 함수를 사용하는 대신 모든 논리를 한 곳에 유지할 수 있습니다 ( Action<>따라서 수십 줄이

Action<blah,blah> f = { foo };

List1.ForEach(p => f(p))
List2.ForEach(p => f(p))

기타...

논리는 한 곳에 있으며 당신은 수업을 오염시키지 않았습니다.


foreach (List1.Concat (List2) .Concat (List3)의 var p) {... 작업 수행 ...}
Cameron MacFarland

처럼 간단하다면 f(p), { }속기를 생략하십시오 : for (var p in List1.Concat(List2).Concat(List2)) f(p);.
spoulson

3

대부분의 LINQ 확장 메서드는 결과를 반환합니다. ForEach는 아무것도 반환하지 않으므로이 패턴에 맞지 않습니다.


2
foreach는 델리게이트는 다른 형태의 작업 <T> 사용
아론 웰

2
leppie의 권리를 제외하고 모든 Enumerable 확장 메소드는 값을 리턴하므로 ForEach는 패턴에 맞지 않습니다. 대표자들은 이것에 들어오지 않습니다.
Cameron MacFarland

확장 메소드가 결과를 리턴하지 않아도된다는 것만으로 자주 사용되는 오퍼레이션을 호출하는 방법을 추가 할 수 있습니다.
Aaron Powell

1
사실, 슬래시. 값을 반환하지 않으면 어떻게됩니까?
TraumaPony

1
@Martijn 즉public IEnumerable<T> Foreach<T>(this IEnumerable<T> items, Action<T> action) { /* error check */ foreach (var item in items) { action(item); yield return item; } }
McKay

3

F # (다음 버전의 .NET에 있음)이있는 경우 다음을 사용할 수 있습니다.

Seq.iter doSomething myIEnumerable


9
따라서 Microsoft는 C # 및 VB에서 IEnumerable에 ForEach 메서드를 제공하지 않기로 결정했습니다. 순전히 기능이 없기 때문입니다. 그러나 그들은 더 기능적인 언어 F #으로 그것을 제공했습니다 (이름 iter). 그들의 논리는 나를 암시합니다.
Scott Hutchinson 17시

3

부분적으로 언어 디자이너가 철학적 관점에서 동의하지 않기 때문입니다.

  • 기능이없는 (및 테스트 중 ...) 기능이있는 것보다 작업이 적습니다.
  • 실제로 짧지는 않습니다 (일부 함수 함수가있는 곳이 있지만 기본 용도는 아닙니다).
  • linq가 아닌 부작용을 갖는 것이 목적입니다.
  • 우리가 이미 가지고있는 기능과 동일한 작업을 수행하는 다른 방법이있는 이유는 무엇입니까? (각 키워드)

https://blogs.msdn.microsoft.com/ericlippert/2009/05/18/foreach-vs-foreach/


2

무언가를 반환하고 싶을 때 select를 사용할 수 있습니다. 그렇지 않으면 컬렉션의 어떤 것도 수정하고 싶지 않기 때문에 ToList를 먼저 사용할 수 있습니다.


a) 이것은 질문에 대한 답변이 아닙니다. b) ToList에는 추가 시간과 메모리가 필요합니다.
짐 발터

2

나는 그것에 대해 블로그 게시물을 썼습니다 : http://blogs.msdn.com/kirillosenkov/archive/2009/01/31/foreach.aspx

.NET 4.0에서이 방법을 보려면 여기에서 투표 할 수 있습니다. http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=279093


이것이 구현 되었습니까? 나는 생각하지 않지만 그 티켓을 대체하는 다른 URL이 있습니까? MS 연결이 중단되었습니다
knocte

이것은 구현되지 않았습니다. github.com/dotnet/runtime/issues
Kirill Osenkov


2

나입니까 아니면 List <T>입니다. 각각 Linq에 의해 거의 사용되지 않습니다. 원래는

foreach(X x in Y) 

여기서 Y는 단순히 IEnumerable (Pre 2.0)이어야하고 GetEnumerator ()를 구현해야했습니다. 생성 된 MSIL을 보면 정확히 동일하다는 것을 알 수 있습니다.

IEnumerator<int> enumerator = list.GetEnumerator();
while (enumerator.MoveNext())
{
    int i = enumerator.Current;

    Console.WriteLine(i);
}

( MSIL에 대해서는 http://alski.net/post/0a-for-foreach-forFirst-forLast0a-0a-.aspx 참조 )

그런 다음 DotNet2.0에서 Generics와 List가 나왔습니다. Foreach는 항상 Vistor 패턴을 구현 한 것으로 느꼈습니다 (Gamma, Helm, Johnson, Vlissides의 디자인 패턴 참조).

물론 3.5에서는 대신 Lambda를 사용하여 동일한 효과를 얻을 수 있습니다. 예를 들어 http://dotnet-developments.blogs.techtarget.com/2008/09/02/iterators-lambda-and-linq-oh- 나의/


1
"foreach"에 대해 생성 된 코드에는 try-finally 블록이 있어야한다고 생각합니다. T의 IEnumerator는 IDisposable 인터페이스에서 상속하기 때문입니다. 따라서 생성 된 finally 블록에서 T 인스턴스의 IEnumerator가 삭제되어야합니다.
Morgan Cheng

2

Aku의 답변 을 확장하고 싶습니다 .

열거 가능한 전체를 먼저 반복하지 않고 부작용의 목적으로 만 메소드를 호출하려면 다음을 사용할 수 있습니다.

private static IEnumerable<T> ForEach<T>(IEnumerable<T> xs, Action<T> f) {
    foreach (var x in xs) {
        f(x); yield return x;
    }
}

1
이 같은입니다Select(x => { f(x); return x;})
짐 Balter

0

ForEach <T>는 foreach 키워드가 런타임 검사되는 컴파일 시간 유형 검사를 수행한다고 아직 아무도 지적하지 않았습니다.

두 가지 방법 모두 코드에서 사용되는 리팩토링을 수행 한 후에 foreach 문제를 찾기 위해 테스트 실패 / 런타임 실패를 찾아 내야했기 때문에 .ForEach를 선호합니다.


5
이것이 사실입니까? foreach컴파일 시간을 덜 확인하는 방법을 알지 못합니다 . 물론, 컬렉션이 제네릭이 아닌 IEnumerable 인 경우 루프 변수는가 object되지만 가상의 ForEach확장 방법 에서도 마찬가지입니다 .
Richard Poole

1
이것은 허용되는 답변에 언급되어 있습니다. 그러나 단순히 잘못되었습니다 (위의 Richard Poole도 정확합니다).
짐 발터
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.