제네릭 메서드를 숫자 유형으로 제한하는 제약 조건이 있나요?

StackOverflow https://stackoverflow.com/questions/32664

  •  09-06-2019
  •  | 
  •  

문제

제네릭 유형 인수를 제한하는 제네릭 방법이 있는지 누구든지 말해 줄 수 있습니까? T 다음에만:

  • Int16
  • Int32
  • Int64
  • UInt16
  • UInt32
  • UInt64

나는 알고있다 where 키워드가 있지만 인터페이스를 찾을 수 없습니다. 오직 이러한 유형,

다음과 같은 것 :

static bool IntegerFunction<T>(T value) where T : INumeric 
도움이 되었습니까?

해결책

C #은이를 지원하지 않습니다. Hejlsberg는 Bruce Eckel과의 인터뷰 에서 기능을 구현하지 않은 이유를 설명했습니다.

<인용구>

추가 된 복잡성이 적은 수율의 가치가 있다는 것은 확실하지 않습니다. 원하는 작업이 제약 시스템에서 직접 지원되지 않는 경우 팩토리 패턴으로 수행 할 수 있습니다. 예를 들어, Matrix<T>를 가질 수 있으며 해당 Matrix에서 내적 방법을 정의하고 싶습니다. 물론 이는 궁극적으로 두 개의 T를 곱하는 방법을 이해해야한다는 것을 의미하지만, 적어도 Tint, double 또는 float 인 경우에는 제약으로 말할 수 없습니다. 하지만 당신이 할 수있는 일은 당신의 MatrixCalculator<T>를 인수로 받아들이도록하는 것이고, Calculator<T>에는 multiply라는 방법이 있습니다. 이를 구현하고 Matrix에 전달합니다.

그러나 이것은 사용자가 사용하려는 각 유전자 태그 코드에 대해 고유 한 유전자 태그 코드 구현을 제공해야하는 상당히 복잡한 코드로 이어집니다. 확장 가능할 필요가없는 한, 즉 Calculator<T>T와 같은 고정 된 수의 유형 만 지원하려는 경우 비교적 간단한 인터페이스를 사용할 수 있습니다. 라코 디스

( GitHub Gist의 최소 구현 )

그러나 사용자가 고유 한 사용자 정의 유형을 제공 할 수 있도록하려면 즉시이 구현을 열어 사용자가 고유 한 int 인스턴스를 제공 할 수 있도록해야합니다. 예를 들어 사용자 정의 십진 부동 소수점 구현 인 double를 사용하는 행렬을 인스턴스화하려면 다음 코드를 작성해야합니다. 라코 디스

… 그리고 Calculator에 대한 모든 구성원을 구현합니다.

안타깝게도 동일한 제한 사항을 공유하는 대안은 Sergey Shandar의 답변에서 논의 된대로 정책 클래스로 작업하는 것입니다. .

다른 팁

이 질문의 인기와 이러한 기능에 대한 관심을 고려할 때 아직 T4에 대한 답변이 없다는 사실에 놀랐습니다.

이 샘플 코드에서는 강력한 템플릿 엔진을 사용하여 컴파일러가 제네릭을 사용하여이면에서 수행하는 작업을 수행하는 방법에 대한 매우 간단한 예를 보여 드리겠습니다.

후프를 거치고 컴파일 시간의 확실성을 희생하는 대신 원하는 모든 유형에 대해 원하는 함수를 생성하고 적절하게 사용할 수 있습니다 (컴파일 시간에!).

방법 :

  • GenericNumberMethodTemplate.tt 라는 새 텍스트 템플릿 파일을 만듭니다.
  • 자동 생성 된 코드를 제거합니다 (대부분은 유지되지만 일부는 필요하지 않음).
  • 다음 스 니펫을 추가합니다. 라코 디스

    그게 다입니다. 이제 끝났습니다.

    이 파일을 저장하면 자동으로 다음 소스 파일로 컴파일됩니다. 라코 디스

    main 메서드에서 컴파일 시간이 확실 함을 확인할 수 있습니다. 라코 디스

    여기에 이미지 설명 입력

    한 가지 언급을 먼저하겠습니다. 아니요, 이것은 DRY 원칙을 위반하는 것이 아닙니다. DRY 원칙은 응용 프로그램을 유지 관리하기 어렵게 만드는 코드를 여러 위치에 복제하는 것을 방지하는 것입니다.

    여기에서는 전혀 그렇지 않습니다. 변경을 원하면 템플릿 (모든 세대를위한 단일 소스!)을 변경하면됩니다.

    사용자 정의 정의와 함께 사용하려면 생성 된 코드에 네임 스페이스 선언을 추가하고 (자체 구현을 정의 할 것과 동일한 지 확인) 클래스를 partial로 표시합니다. 그런 다음 최종 컴파일에 포함되도록 다음 줄을 템플릿 파일에 추가합니다. 라코 디스

    솔직히 말해서 이건 정말 멋지네요.

    면책 조항 :이 샘플은 Kevin Hazzard 및 Jason Bock의 .NET 메타 프로그래밍, Manning Publications <의 영향을 많이 받았습니다. / a>.

이에 대한 제약은 없습니다.숫자 계산에 제네릭을 사용하려는 모든 사람에게 이는 실제 문제입니다.

더 나아가 필요하다고 말하겠습니다. 라코 디스

또는 라코 디스

안타깝게도 인터페이스, 기본 클래스 및 키워드 struct (값 유형이어야 함), class (참조 유형이어야 함) 및 new() (기본 생성자가 있어야 함) 만 있습니다.

번호를 여기 codeproject <와 같은 다른 것으로 (INullable<T>와 유사) 래핑 할 수 있습니다./ a>. <시간>

연산자를 반영하거나 유형을 확인하여 런타임에 제한을 적용 할 수 있지만 처음에는 제네릭을 사용하는 이점을 잃게됩니다.

정책 사용 해결 방법 : 라코 디스

알고리즘 : 라코 디스

사용법 : 라코 디스

솔루션은 컴파일 타임에 안전합니다. CityLizard Framework 는 .NET 4.0 용 컴파일 된 버전을 제공합니다.파일은 lib / NETFramework4.0 / CityLizard.Policy.dll입니다.

Nuget에서도 사용할 수 있습니다. https://www.nuget.org/packages/CityLizard/ . CityLizard.Policy.I 구조를 참조하세요.

이 질문은 FAQ 질문이므로 위키로 게시합니다(이전에 유사한 내용을 게시했지만 이는 오래된 질문이므로).그래도...

어떤 버전의 .NET을 사용하고 있나요?.NET 3.5를 사용하고 있다면 일반 연산자 구현 ~에 기타 (무료 등).

여기에는 다음과 같은 방법이 있습니다. T Add<T>(T x, T y), 그리고 다른 유형의 산술을 위한 다른 변형(예: DateTime + TimeSpan).

또한 이는 모든 내장형, 리프트형 및 맞춤형 연산자에 대해 작동하며 성능을 위해 대리자를 캐시합니다.

이것이 까다로운 이유에 대한 추가 배경은 다음과 같습니다. 여기.

당신은 또한 그것을 알고 싶을 수도 있습니다 dynamic (4.0) sort-of는 이 문제를 간접적으로도 해결합니다.

dynamic x = ..., y = ...
dynamic result = x + y; // does what you expect

안타깝게도이 인스턴스의 where 절에서만 struct를 지정할 수 있습니다.Int16, Int32 등을 구체적으로 지정할 수 없다는 것이 이상해 보이지만 where 절에서 값 유형을 허용하지 않기로 결정한 근본적인 구현 이유가 있다고 확신합니다.

유일한 해결책은 불행히도 컴파일 타임에 문제가 발생하지 않도록하는 런타임 검사를 수행하는 것입니다.다음과 같이됩니다 .- 라코 디스

내가 알고있는 약간 못 생겼지 만 최소한 필요한 제약을 제공합니다.

또한이 구현에 대해 가능한 성능 영향을 조사 할 것입니다. 아마도 더 빠른 방법이있을 것입니다.

아마 가장 가까운 방법은 라코 디스

다음을 수행 할 수 있는지 확실하지 않음 라코 디스

그렇게 특정한 것을 위해, 왜 각 유형에 대해 오버로드가있는 것이 아니라 목록이 너무 짧고 메모리 사용량이 적을 수 있습니다.

템플릿을 유형으로 제한하는 방법은 없지만 유형에 따라 다른 작업을 정의 할 수 있습니다.일반 숫자 패키지의 일부로 두 값을 추가하려면 일반 클래스가 필요했습니다. 라코 디스 typeofs는 컴파일 타임에 평가되므로 if 문은 컴파일러에 의해 제거됩니다.컴파일러는 또한 가짜 캐스트를 제거합니다.그래서 뭔가 컴파일러에서 라코 디스

이러한 문제를 해결하기 위해 작은 라이브러리 기능을 만들었습니다.

대신 : 라코 디스

다음과 같이 작성할 수 있습니다. 라코 디스

다음에서 소스 코드를 찾을 수 있습니다. https : //codereview.stackexchange.com/questions/26022/improvement-requested-for-generic-calculator-and-generic-number

삼 저드슨과 똑같은데, 왜 정수만하나요?이 경우 원하는 모든 유형을 담을 수있는 도우미 클래스 등을 만들 수 있습니다.

원하는 것이 정수뿐이라면 제네릭이 아닌 제네릭을 사용하지 마십시오.또는 더 나은 방법은 유형을 확인하여 다른 유형을 거부하는 것입니다.

연습의 요점은 무엇입니까?

사람들이 이미 지적했듯이, 가장 큰 항목을 취하는 비 제네릭 함수를 가질 수 있으며 컴파일러는 자동으로 더 작은 int를 변환합니다. 라코 디스

함수가 성능에 중요한 경로 (아주 드물게 IMO)에있는 경우 필요한 모든 기능에 과부하를 제공 할 수 있습니다. 라코 디스

나는 당신이 외부에서 처리 할 수있는 일반적인 것을 사용합니다 ... 라코 디스

이 제한은 제네릭 유형에 대한 연산자를 오버로드하려고 할 때 영향을 미쳤습니다."INumeric"제약이 없었고 다른 이유로 stackoverflow의 좋은 사람들이 기꺼이 제공하기 때문에 제네릭 유형에 대한 작업을 정의 할 수 없습니다.

나는 다음과 같은 것을 원했다 라코 디스

.net4 동적 런타임 타이핑을 사용하여이 문제를 해결했습니다. 라코 디스

dynamic 사용에 대한 두 가지 사항은 다음과 같습니다.

  1. 실적.모든 값 유형은 상자에 담겨 있습니다.
  2. 런타임 오류.컴파일러를 "이겼지 만"형식 안전성을 잃습니다.제네릭 유형에 연산자가 정의되어 있지 않으면 실행 중에 예외가 발생합니다.

아직 '좋은'해결책은 없습니다.그러나 Haacked가 위에서 보여준 것처럼 가상의 'INumeric'제약 조건에 대한 많은 잘못된 맞춤을 배제하기 위해 type 인수를 상당히 좁힐 수 있습니다.

static bool IntegerFunction (T 값) 여기서 T : IComparable, IFormattable, IConvertible, IComparable , IEquatable , struct {...

.NET 숫자 기본 유형은 계산에 사용할 수있는 공통 인터페이스를 공유하지 않습니다. 그러한 작업을 수행하는 고유 한 인터페이스 (예 : ISignedWholeNumber)를 정의하고, 단일 Int16, Int32 등을 포함하는 구조를 정의하고, 이러한 인터페이스를 구현 한 다음, ISignedWholeNumber로 제한되는 일반 유형을 허용하는 메소드를 가질 수 있습니다. 숫자 값을 구조 유형으로 변환하는 것은 성가신 일입니다.

대체 접근 방식은 Int64Converter<T>, bool Available {get;};, Int64 GetInt64(T value)에 대한 static 속성 T FromInt64(Int64 value) 및 static delegates를 사용하여 static class bool TryStoreInt64(Int64 value, ref T dest)를 정의하는 것입니다. 클래스 생성자는 하드 코드를 사용하여 알려진 유형에 대한 대리자를로드 할 수 있으며, Reflection을 사용하여 유형 T가 적절한 이름과 서명이있는 메서드를 구현하는지 여부를 테스트 할 수 있습니다 (Int64를 포함하고 숫자를 나타내는 구조체와 같은 경우, 하지만 사용자 지정 ToString() 메서드가 있습니다.) 이 접근 방식은 컴파일 타임 유형 검사와 관련된 이점을 잃게되지만, 여전히 박싱 작업을 피할 수 있으며 각 유형은 한 번만 "검사"하면됩니다. 그 후 해당 유형과 관련된 작업은 델리게이트 디스패치로 대체됩니다.

.NET 4.0 이상을 사용하는 경우 메소드 인수로 dynamic 을 사용하고 dynamic 이 전달되었는지 런타임에서 확인할 수 있습니다. 인수 유형은 숫자 / 정수 유형입니다.

전달 된 동적 유형이 숫자 / 정수 유형이 아닌 경우 예외를 발생시킵니다.

아이디어를 구현하는 짧은 코드의 예는 다음과 같습니다. 라코 디스

물론이 솔루션은 런타임에만 작동하지만 컴파일 타임에는 작동하지 않습니다.

항상 컴파일 타임에 작동하고 절대 런타임에는 작동하지 않는 솔루션을 원한다면 public 이 오버로드 된 공용 구조체 / 클래스로 dynamic 을 래핑해야합니다. 생성자는 원하는 유형의 인수 만 허용하고 구조체 / 클래스에 적절한 이름을 제공합니다.

래핑 된 동적 은 항상 클래스 / 구조체의 비공개 구성원이고 구조체 / 클래스의 유일한 구성원이며 유일한 구조체 / 클래스의 멤버는 "값"입니다.

필요한 경우 클래스 / 구조체의 비공개 동적 멤버에 대해 원하는 유형으로 작동하는 공용 메소드 및 / 또는 연산자를 정의하고 구현해야합니다.

구조 / 클래스에는 동적 을 인수로 받아들이는 특수 / 고유 생성자가 "값"이라는 비공개 동적 멤버 만 초기화하지만 < 이 생성자의 strong> modifier 는 물론 private 입니다.

클래스 / 구조체가 준비되면 인수의 IntegerFunction 유형을 정의 된 클래스 / 구조체로 정의합니다.

아이디어를 구현하는 코드의 예는 다음과 같습니다. 라코 디스

코드에서 동적 을 사용하려면 Microsoft.CSharp 참조를 추가 해야합니다.

.NET 프레임 워크의 버전이 4.0 미만 / 미만 / 미만이고 해당 버전에서 동적 이 정의되지 않은 경우 개체 를 대신 사용해야합니다. 정수 유형으로 변환하는 것은 문제가되므로 개체 대신 동적 을 사용할 수 있도록 가능하면 .NET 4.0 이상을 사용하는 것이 좋습니다.

원하는 것이 하나의 숫자 유형 을 사용하는 것이라면 using를 사용하여 C ++에서 별칭과 유사한 것을 만드는 것을 고려할 수 있습니다.

매우 일반적인 대신 라코 디스

당신은 할 수있었습니다 라코 디스

그러면 필요한 경우 double에서 int 또는 다른 것으로 쉽게 이동할 수 있지만 동일한 프로그램에서 ComputeSomethingdouble와 함께 int를 사용할 수 없습니다.

그러면 왜 모든 doubleint로 바꾸지 않습니까?입력이 double 또는 double인지 여부에 관계없이 방법이 int를 사용할 수 있기 때문입니다.별칭을 사용하면 동적 유형을 사용하는 변수를 정확히 알 수 있습니다.

숫자 유형과 문자열을 처리해야하는 비슷한 상황이있었습니다.약간 기괴한 조합 인 것 같지만 끝났습니다.

다시 많은 사람들처럼 저는 제약 조건을 살펴보고 지원해야하는 많은 인터페이스를 생각해 냈습니다.그러나 a) 100 % 방수가 아니었고 b)이 긴 제약 목록을 보는 새로운 사람은 즉시 매우 혼란 스러울 것입니다. 그래서 내 접근 방식은 모든 논리를 제약없이 일반 메서드에 넣는 것이지만 해당 일반 메서드를 비공개로 만드는 것이 었습니다.그런 다음 처리하려는 유형을 명시 적으로 처리하는 공용 메서드로 노출했습니다. 내 마음에 코드는 깨끗하고 명시 적입니다. 예 : 라코 디스

모두 상속하는 단일 인터페이스 또는 기본 클래스가 없으므로 (다른 클래스에서도 상속되지 않음) 간단한 대답은 '아니요'입니다.

왜 이것이 문제인지 궁금합니다.정수에 대해서만 수행 할 수있는 IntegerFunction 클래스 내에서 무엇을하고 싶습니까?

제네릭을 오해하신 것 같습니다. 수행하려는 작업이 특정 데이터 유형에만 적합하다면 "일반적인"작업을 수행하지 않는 것입니다.

또한 함수가 int 데이터 유형에서만 작동하도록 허용하기를 원하므로 각 특정 크기에 대해 별도의 함수가 필요하지 않아야합니다. 가장 큰 특정 유형의 매개 변수를 사용하면 프로그램이 더 작은 데이터 유형을 자동으로 업 캐스트 할 수 있습니다. (즉, Int16을 전달하면 호출시 Int64로 자동 변환됩니다.)

함수에 전달되는 int의 실제 크기에 따라 다른 작업을 수행하는 경우 수행중인 작업을 수행하려고해도 진지하게 재고해야한다고 생각합니다. 언어를 속 여야한다면 원하는 작업을 수행하는 방법보다는 수행하려는 작업에 대해 조금 더 생각해야합니다.

다른 모든 방법이 실패하면 Object 유형의 매개 변수를 사용할 수 있으며 매개 변수 유형을 확인하고 적절한 조치를 취하거나 예외를 발생시켜야합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top