C# - dynamic 사용 시 internal 멤버에 대한 RuntimeBinderException 예외가 발생하는 문제
지난 글에서,
C# - dynamic 예약어 사용 시 런타임에 "Microsoft.CSharp.RuntimeBinder.RuntimeBinderException" 예외가 발생하는 경우
; https://www.sysnet.pe.kr/2/0/14032
RuntimeBinderException 예외가 발생할 수 있는 사례를 한 설명했는데요, 재미있는 건 이에 대해 issue를 넣었더니,
dynamic call is not working. #120927
; https://github.com/dotnet/runtime/issues/120927
담당자가 위의 문제가 "by design"이라면서 명시적 인터페이스 문제라고 답변을 주었습니다. 물론 이것은 지난 글에 설명했듯이,
C# - dynamic과 "Explicit Interface Implementation"의 문제
; https://www.sysnet.pe.kr/2/0/14033
완전히 다른 문제입니다. 그래서 질문의 이해를 돕기 위해 좀 더 단순한 재현 코드를 작성해 봤는데요, 조건은 1) 어셈블리를 나눠야 하고, 2) public 접근자가 아닌 internal 멤버에 대해 dynamic 호출을 시도할 때 예외가 발생하는 걸로 정리가 됩니다. 그래서 이렇게 재현할 수 있습니다.
// ClassLibrary1 프로젝트
namespace ClassLibrary1;
public interface IInternal
{
string Name { get; }
}
internal class InternalClass : IInternal
{
public string Name
{
get
{
return "test";
}
}
}
public class Class1
{
public IInternal InternalObj
{
get
{
// internal로 구현한 클래스지만 public 접근자를 가진 인터페이스로 반환
return new InternalClass();
}
}
}
// ConsoleApp 프로젝트
using ClassLibrary1;
namespace ConsoleApp2;
internal class Program
{
static void Main(string[] args)
{
Class1 obj = new Class1();
dynamic dnObj = obj.InternalObj; // IInternal 타입의 인스턴스를 dynamic으로 변환
Console.WriteLine(dnObj.Name); // 예외 발생
// Unhandled exception. Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'object' does not contain a definition for 'Name'
}
}
따라서 만약 소스 코드를 바꿀 수 있다면 저 조건 중의 하나만 해결해도 됩니다. 가령 위의 예제에서 InternalClass는 internal 접근자로 정의된 클래스인데, 만약 이것을 public 접근자로 바꾸면,
// internal class InternalClass : IInternal
public class InternalClass : IInternal // public으로 변경 후, 실행 시 dynamic 호출 정상 동작
{
// ...
}
dynamic 호출이 정상적으로 동작합니다. 그러니까, 저건 분명히 "Explicit interface implementation" 문제는 아닙니다.
어쨌든 현재 저런 상황에서 dynamic 호출을 시도하면 RuntimeBinderException 예외가 발생하는데, 이슈가 어떻게 처리될지 궁금하군요. ^^
일단, 해결되든 안 되든 지금 당장 해결책이 필요하다면 편리한
(지난 글에 제시했던) dynamic 문법 대신, 사용자가 풀어서 호출하는 걸로 바꿔야 합니다.
Class1 obj = new Class1();
// dynamic을 사용한 호출 대신,
{
dynamic dnObj = obj.InternalObj;
Console.WriteLine(dnObj.Name); // 예외 발생
}
// 이렇게 풀어서 사용
{
object objInstance = obj.InternalObj;
// dynamic 예약어를 사용한 경우, C# 컴파일러가 자동 생성 코드가 사용하는 타입
// Type type = typeof(Program);
// 하지만 실제 런타임 시 타입을 넣어주면 예외가 발생하지 않음
Type type = objInstance.GetType();
CallSite<Func<CallSite, object, object>>? p0 = CallSite<Func<CallSite, object, object>>.Create(
Binder.GetMember(CSharpBinderFlags.None, "Name", type, new CSharpArgumentInfo[]
{
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
}));
object? result = p0.Target(p0, objInstance);
Console.WriteLine(result);
}
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]