program story

C #이 내 제네릭 유형을 유추하지 않는 이유는 무엇입니까?

inputbox 2020. 11. 21. 14:08
반응형

C #이 내 제네릭 유형을 유추하지 않는 이유는 무엇입니까?


나는 일반적인 방법으로 많은 Funcy 재미 (재미 의도)를 가지고 있습니다. 대부분의 경우 C # 유형 추론은 내 제네릭 메서드에서 사용해야하는 제네릭 인수를 알아낼만큼 똑똑하지만 이제는 C # 컴파일러가 성공하지 못하는 디자인이 있습니다. 올바른 유형.

이 경우 컴파일러가 약간 멍청한 지 누구든지 말해 줄 수 있습니까? 아니면 내 일반 인수를 추론 할 수없는 매우 명확한 이유가 있습니까?

코드는 다음과 같습니다.

클래스 및 인터페이스 정의 :

interface IQuery<TResult> { }

interface IQueryProcessor
{
    TResult Process<TQuery, TResult>(TQuery query)
        where TQuery : IQuery<TResult>;
}

class SomeQuery : IQuery<string>
{
}

컴파일되지 않는 일부 코드 :

class Test
{
    void Test(IQueryProcessor p)
    {
        var query = new SomeQuery();

        // Does not compile :-(
        p.Process(query);

        // Must explicitly write all arguments
        p.Process<SomeQuery, string>(query);
    }
}

왜 이런거야? 내가 여기서 무엇을 놓치고 있습니까?

다음은 컴파일러 오류 메시지입니다 (상상력에 크게 영향을주지 않음).

IQueryProcessor.Process (TQuery) 메서드의 형식 인수는 사용법에서 유추 할 수 없습니다. 형식 인수를 명시 적으로 지정해보십시오.

C #이이를 추론 할 수 있어야한다고 생각하는 이유는 다음과 같습니다.

  1. 구현하는 개체를 제공합니다 IQuery<TResult>.
  2. IQuery<TResult>유형이 구현하는 유일한 버전은 IQuery<string>이므로 TResult는이어야합니다 string.
  3. 이 정보를 사용하여 컴파일러에는 TResult 및 TQuery가 있습니다.

해결책

저에게 가장 좋은 해결책은 IQueryProcessor인터페이스 를 변경 하고 구현에서 동적 타이핑을 사용하는 것입니다.

public interface IQueryProcessor
{
    TResult Process<TResult>(IQuery<TResult> query);
}

// Implementation
sealed class QueryProcessor : IQueryProcessor {
    private readonly Container container;

    public QueryProcessor(Container container) {
        this.container = container;
    }

    public TResult Process<TResult>(IQuery<TResult> query) {
        var handlerType =
            typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult));
        dynamic handler = container.GetInstance(handlerType);
        return handler.Handle((dynamic)query);
    }
}

IQueryProcessor인터페이스는 지금에 소요 IQuery<TResult>매개 변수입니다. 이렇게하면 a를 반환 할 수 TResult있으며 소비자의 관점에서 문제를 해결할 수 있습니다 . 구체적인 쿼리 유형이 필요하기 때문에 (제 경우에는) 실제 구현을 얻기 위해 구현에서 리플렉션을 사용해야합니다. 그러나 여기에 우리를 위해 반성을 할 구제에 동적 타이핑이 있습니다. 기사 에서 이에 대한 자세한 내용을 읽을 수 있습니다 .


많은 사람들이 C #이 제약 조건을 기반으로 추론하지 않는다고 지적했습니다. 정확하고 질문과 관련이 있습니다. 추론은 인수 와 해당 형식 매개 변수 유형 을 검사하여 이루어지며 , 이것이 추론 정보의 유일한 소스입니다.

많은 사람들이이 기사에 링크했습니다.

http://blogs.msdn.com/b/ericlippert/archive/2007/11/05/c-3-0-return-type-inference-does-not-work-on-member-groups.aspx

이 기사는 구식이며 질문과 관련이 없습니다. C # 3.0에서 내린 디자인 결정을 설명하기 때문에이 문서는 대부분 해당 문서에 대한 응답을 기반으로 C # 4.0에서 되돌 렸습니다. 기사에 그 효과에 대한 업데이트를 방금 추가했습니다.

이 문서는 메서드 그룹 인수에서 제네릭 대리자 형식 매개 변수로의 반환 형식 유추 에 대한 것이기 때문에 관련이 없습니다 . 그것은 원래 포스터가 요구하는 상황이 아닙니다.

읽어야 할 관련 기사는 다음과 같습니다.

http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx

업데이트 : C # 7.3으로 인해 제약 조건이 적용될 때 규칙이 약간 변경되어 위의 10 년 된 기사가 더 이상 정확하지 않다는 소식을 들었습니다. 시간이 있으면 이전 동료가 변경 한 사항을 검토하고 새 블로그에 수정 사항을 게시 할 가치가 있는지 확인합니다. 그때까지는주의를 기울이고 실제로 C # 7.3이 수행하는 작업을 확인하십시오.


C #은 제네릭 메서드의 반환 형식을 기반으로 제네릭 형식을 유추하지 않고 메서드에 대한 인수 만 유추합니다.

또한 제약 조건을 형식 유추의 일부로 사용하지 않으므로 형식을 제공하는 일반 제약 조건이 제거됩니다.

자세한 내용 은 주제에 대한 Eric Lippert의 게시물을 참조하십시오 .


유형을 추론하기 위해 제약 조건을 사용하지 않습니다. 오히려 가능한 경우 유형을 유추 한 다음 제약 조건을 확인합니다.

따라서 매개 변수 TResult와 함께 사용할 수있는 유일한 방법 SomeQuery이지만이를 볼 수는 없습니다.

또한를 SomeQuery구현 IQuery<int>하는 것이 완벽하게 가능하다는 점에 유의하십시오. 이것이 컴파일러에 대한 제한이 나쁜 생각이 아닌 이유 중 하나입니다.


사양은 이것을 매우 명확하게 설명합니다.

7.4.2 절 유형 추론

제공된 인수 수가 메서드의 매개 변수 수와 다르면 추론이 즉시 실패합니다. 그렇지 않으면 제네릭 메서드에 다음 서명이 있다고 가정합니다.

Tr M (T1 x1… Tm xm)

M (E1… Em) 형식의 메서드 호출을 사용하여 형식 유추의 작업은 호출 M (E1… Em)이 유효 해 지도록 각 형식 매개 변수 X1… Xn에 대해 고유 한 형식 인수 S1… Sn찾는 것입니다.

As you can see, the return type is not used for type inference. If the method call does not map directly to the type arguments inference immediately fails.

The compiler does not just assume that you wanted string as the TResult argument, nor can it. Imagine a TResult derived from string. Both would be valid, so which to choose? Better to be explicit.


The why has been well answered but there is an alternative solution. I face the same issues regularly however dynamic or any solution using reflection or allocating data is out of question in my case (joy of video games...)

So instead I pass the return as an out parameters which is then correctly inferred.

interface IQueryProcessor
{
     void Process<TQuery, TResult>(TQuery query, out TResult result)
         where TQuery : IQuery<TResult>;
}

class Test
{
    void Test(IQueryProcessor p)
    {
        var query = new SomeQuery();

        // Instead of
        // string result = p.Process<SomeQuery, string>(query);

        // You write
        string result;
        p.Process(query, out result);
    }
}

The only drawback I can think of is that it's prohibiting usage of 'var'.


I won't go into the why again, I have no illusions of being able to do a better explanation than Eric Lippert.

However, there is a solution that doesn't require late binding or extra parameters to your method call. It's not super intuitive however, so I'll leave it to the reader to decide if it's an improvement.

First off, modify IQuery to make it self-referencing:

public interface IQuery<TQuery, TResult> where TQuery: IQuery<TQuery, TResult>
{
}

Your IQueryProcessor would look like this:

public interface IQueryProcessor
{
    Task<TResult> ProcessAsync<TQuery, TResult>(IQuery<TQuery, TResult> query)
        where TQuery: IQuery<TQuery, TResult>;
}

An actual query type:

public class MyQuery: IQuery<MyQuery, MyResult>
{
    // Neccessary query parameters
}

An implementation of the processor might look like:

public Task<TResult> ProcessAsync<TQuery, TResult>(IQuery<TQuery, TResult> query)
    where TQuery: IQuery<TQuery, TResult>
{
    var handler = serviceProvider.Resolve<QueryHandler<TQuery, TResult>>();
    // etc.
}

Another workaround for this issue is to add additional parameter for type resolution. For instance we can add the following extension:

static class QueryProcessorExtension
{
    public static TResult Process<TQuery, TResult>(
        this IQueryProcessor processor, TQuery query,
        //Additional parameter for TQuery -> IQuery<TResult> type resolution:
        Func<TQuery, IQuery<TResult>> typeResolver)
        where TQuery : IQuery<TResult>
    {
        return processor.Process<TQuery, TResult>(query);
    }
}

Now we can use this extension as follows:

void Test(IQueryProcessor p)
{
    var query = new SomeQuery();

    //You can now call it like this:
    p.Process(query, x => x);
    //Instead of
    p.Process<SomeQuery, string>(query);
}

Which is far from ideal but much better than providing types explicitly.

P.S. Related links for this issue in dotnet repository:

https://github.com/dotnet/csharplang/issues/997

https://github.com/dotnet/roslyn/pull/7850

참고URL : https://stackoverflow.com/questions/8511066/why-doesnt-c-sharp-infer-my-generic-types

반응형