디자인 타임에 변수를 사용하여 선언 된 변수를 어떻게 안정적으로 확인할 수 있습니까?
이맥스에서 C #을 완성하는 (지능형) 시설에서 일하고 있습니다.
.NET 리플렉션을 사용하여 가능한 완료를 결정합니다.
이렇게 결제 완료되는 항목의 유형을 알아야합니다. 사용 가능합니다. Int32 인 경우 별도의 세트 등이 있습니다.
emacs에서 사용 가능한 코드 어휘 분석기 / 파서 패키지 인 시맨틱을 사용하여 변수 선언과 해당 유형을 사용할 수 있습니다. 감안할 때 리플렉션을 사용하여 유형에 대한 메소드 및 특성을 제시하는 것이 간단합니다. (좋아요, emacs 내에서 간단한 간단 하지 않지만 emacs 내부에서 powershell 프로세스를 실행하는 기능을 사용 하면 간단하게 처리합니다. 리플렉션을 수행하고 powershell에로드 한 다음 사용자 .NET 어셈블리를 작성하여 내부에서 실행하지 않습니다. emacs는 명령을 comshell을 통해 powershell에 보내는 응답을 읽을 수 있습니다.
코드 var
가 완료되는 것을 선언 할 때 문제가 발생합니다 . 이는 즉시 발생하지 않으며 작동하지 않음을 의미합니다.
변수를 var
키워드 로 선언 할 때 사용할 수있는 실제 유형을 어떻게 안정적으로 사용할 수 있습니까? 구축하기 위해 실행에 필요가 없습니다. "디자인 타임"에서 결정하고 싶습니다.
지금까지 기존 아이디어가 있습니다.
- 호출하고 호출하십시오.
- 선언문을 추출한다. 예 :`var foo = "문자열 값";`
- `foo.GetType ();`문장을 연결
- 결과 C # 조각을 새 어셈블리로 동적으로 수행
- 어셈블리를 새 AppDomain에로드하고 프 래밍을 실행하고 반환 유형을 가져옵니다.
- 어셈블리를 언로드 및 폐기
나는이 모든 것을하는 방법을 알고있다. 그러나 편집기의 각 완료 요청에 대해 너무 무겁게 들립니다.
매번 새로운 AppDomain이 필요하지 않다고 가정합니다. 여러 임시 어셈블리에 단일 AppDomain을 여러 차례 완료 요청에서 설정 및 해제하는 비용을 상각 할 수 있습니다. 그것은 기본 아이디어의 더 많은 조정입니다.
- IL 검사 및 검사
선언을 모듈로 한 다음 IL을 검사하여 컴파일러가 유추 한 실제 유형을 확인하십시오. 이것이 어떻게 가능할까요? IL을 검사하기 위해 무엇을 꼽을 수 있습니까?
더 좋은 아이디어가 있습니까? 게임? 제안?
편집 -이에 대해 더 이상, 호출 및 호출은 부작용이있을 수 있기 때문에 호출 및 호출은 생각할 수 없습니다. 따라서 첫 번째 옵션은 배제해야합니다.
또한 .NET 4.0이 가정 할 수 있다고 생각합니다.
업데이트 -위에서 언급 한 Eric Lippert가 부드럽게 지적한 정답은 완전 충실도 유형 유추 시스템을 구현하는 것입니다. 디자인 타임에 var의 유형을 안정적으로 결정하는 유일한 방법입니다. 그러나 쉬운 일도 아닙니다. 그런 것을 만들려고한다는 환상이 없기 때문에 2의 바로 가기를 사용하여 관련 코드를 선택하고 다음 결과 IL을 검사합니다.
이것은 실제 완성 시나리오의 공정한 부분 집합에 적용됩니다.
예를 들어, 다음 코드 조각에서? 사용자가 완료를 요청하는 위치입니다. 작동합니다 :
var x = "hello there";
x.?
완성은 x가 인식하고 적절한 것을 제공합니다. 다음 소스 코드를 생성하고이를 수행합니다.
namespace N1 {
static class dmriiann5he { // randomly-generated class name
static void M1 () {
var x = "hello there";
}
}
}
... 그리고 간단한 반사로 IL을 검사합니다.
작동합니다.
var x = new XmlDocument();
x.?
엔진은 생성 된 소스 코드에 적절한 절을 추가하여 선택하는 다음 IL 검사가 동일합니다.
이것도 작동합니다.
var x = "hello";
var y = x.ToCharArray();
var z = y.?
이는 IL 검사가 첫 번째 변수 대신 세 번째 로컬 변수의 유형을 찾아야한다는 것을 의미합니다.
이 :
var foo = "Tra la la";
var fred = new System.Collections.Generic.List<String>
{
foo,
foo.Length.ToString()
};
var z = fred.Count;
var x = z.?
... 이전 예제보다 한 단계 깊습니다.
그러나 작동 하지 않는 인스턴스 멤버 또는 메소드 인수에 대한 시점에 초기화가있는 로컬 변수에서 완료하는 것입니다. 처럼 :
var foo = this.InstanceMethod();
foo.?
LINQ 구문도 없습니다.
완성을 위해 확실히 "제한된 디자인"(정중 한 단어)을 통해 문제를 해결하기 전에 이러한 것들이 얼마나 가치가 있는지 생각해야합니다.
메서드 인수 또는 인스턴스 메서드에 대한 종속성 문제를 해결하는 방법은 생성되고 컴파일 된 다음 IL이 분석되는 코드 조각에서 이러한 항목에 대한 참조를 동일한 유형의 "합성"로컬 변수로 대체하는 것입니다.
또 다른 업데이트 -인스턴스 멤버에 의존하는 변수에 대한 완성이 이제 작동합니다.
내가 한 일은 (시맨틱을 통해) 유형을 조사한 다음 모든 기존 멤버에 대한 합성 대기 멤버를 생성하는 것입니다. 다음과 같은 C # 버퍼의 경우 :
public class CsharpCompletion
{
private static int PrivateStaticField1 = 17;
string InstanceMethod1(int index)
{
...lots of code here...
return result;
}
public void Run(int count)
{
var foo = "this is a string";
var fred = new System.Collections.Generic.List<String>
{
foo,
foo.Length.ToString()
};
var z = fred.Count;
var mmm = count + z + CsharpCompletion.PrivateStaticField1;
var nnn = this.InstanceMethod1(mmm);
var fff = nnn.?
...more code here...
... 컴파일 된 생성 된 코드는 출력 IL에서 로컬 var nnn의 유형을 배울 수 있습니다.
namespace Nsbwhi0rdami {
class CsharpCompletion {
private static int PrivateStaticField1 = default(int);
string InstanceMethod1(int index) { return default(string); }
void M0zpstti30f4 (int count) {
var foo = "this is a string";
var fred = new System.Collections.Generic.List<String> { foo, foo.Length.ToString() };
var z = fred.Count;
var mmm = count + z + CsharpCompletion.PrivateStaticField1;
var nnn = this.InstanceMethod1(mmm);
}
}
}
모든 인스턴스 및 정적 유형 멤버는 스켈레톤 코드에서 사용할 수 있습니다. 성공적으로 컴파일됩니다. 이 시점에서 지역 변수의 유형을 결정하는 것은 Reflection을 통해 간단합니다.
이를 가능하게하는 것은 다음과 같습니다.
- emacs에서 powershell을 실행하는 기능
- C # 컴파일러는 정말 빠릅니다. 내 컴퓨터에서 메모리 내 어셈블리를 컴파일하는 데 약 0.5 초가 걸립니다. 키 입력 간 분석에는 빠르지 않지만 주문형 완성 목록 생성을 지원할만큼 빠릅니다.
아직 LINQ를 살펴 보지 않았습니다.
의미 론적 렉서 / 파서 emacs가 C #에 대해 가지고 있지만 LINQ를 "실행"하지 않기 때문에 훨씬 더 큰 문제가 될 것입니다.
"실제"C # IDE에서이를 효율적으로 수행하는 방법을 설명 할 수 있습니다.
가장 먼저 할 일은 소스 코드의 "최상위"항목 만 분석하는 패스를 실행하는 것입니다. 모든 메서드 본문을 건너 뜁니다. 이를 통해 프로그램의 소스 코드에 어떤 네임 스페이스, 유형 및 메서드 (및 생성자 등)가 있는지에 대한 정보 데이터베이스를 신속하게 구축 할 수 있습니다. 모든 메서드 본문에서 모든 코드 줄을 분석하는 데는 키 입력 사이에 작업을 수행하는 데 너무 오래 걸립니다.
IDE가 메서드 본문 내에서 특정 식의 유형을 계산해야하는 경우- "foo"를 입력했다고 가정합니다. 그리고 우리는 foo의 구성원이 무엇인지 알아 내야합니다. 우리는 같은 일을합니다. 우리는 합리적으로 할 수있는 한 많은 일을 건너 뜁니다.
해당 메서드 내 에서 지역 변수 선언 만 분석하는 패스로 시작 합니다. 이 패스를 실행할 때 "범위"와 "이름"쌍에서 "유형 결정자"로 매핑을 만듭니다. "유형 결정자"는 "필요한 경우이 로컬 유형을 해결할 수 있습니다"라는 개념을 나타내는 개체입니다. 지역 유형을 작업하는 것은 비용이 많이들 수 있으므로 필요한 경우 작업을 연기하고 싶습니다.
이제 모든 로컬 유형을 알려줄 수있는 느리게 구축 된 데이터베이스가 있습니다. 그래서 그 "foo"로 돌아갑니다. -우리 는 관련 표현식이 어떤 문 에 있는지 알아 낸 다음 해당 문에 대해 의미 분석기를 실행합니다. 예를 들어 다음과 같은 메서드 본문이 있다고 가정합니다.
String x = "hello";
var y = x.ToCharArray();
var z = from foo in y where foo.
이제 우리는 foo가 char 유형이라는 것을 알아 내야합니다. 모든 메타 데이터, 확장 메서드, 소스 코드 유형 등을 포함하는 데이터베이스를 구축합니다. x, y 및 z에 대한 유형 결정자가있는 데이터베이스를 구축합니다. 흥미로운 표현이 담긴 진술을 분석합니다. 우리는 구문을 다음과 같이 변환하여 시작합니다.
var z = y.Where(foo=>foo.
foo 유형을 알아 내기 위해서는 먼저 y 유형을 알아야합니다. 그래서이 시점에서 우리는 유형 결정자에게 "y의 유형은 무엇입니까?"라고 묻습니다. 그런 다음 x.ToCharArray ()를 구문 분석하고 "x 유형이 무엇인지"묻는 표현식 평가기를 시작합니다. "현재 컨텍스트에서"문자열 "을 검색해야합니다"라는 유형 결정자가 있습니다. 현재 유형에는 String 유형이 없으므로 네임 스페이스를 살펴 봅니다. 그것도 존재하지 않기 때문에 using 지시문을 살펴보고 "using System"이 있고 System에 String 유형이 있음을 발견합니다. 좋습니다. 이것이 x의 유형입니다.
그런 다음 System.String의 메타 데이터에 ToCharArray 유형을 쿼리하면 System.Char []라고 표시됩니다. 감독자. 그래서 우리는 y에 대한 유형을 가지고 있습니다.
이제 "System.Char []에 Where 메서드가 있습니까?"라고 묻습니다. 아니요. 따라서 using 지시문을 살펴 봅니다. 사용할 수있는 확장 메서드에 대한 모든 메타 데이터를 포함하는 데이터베이스를 이미 미리 계산했습니다.
이제 "OK, Where in scope라는 이름의 확장 메서드가 18 개 있습니다. 그 중 어떤 형식이 System.Char []와 호환되는 첫 번째 형식 매개 변수가 있습니까?" 그래서 우리는 전환성 테스트를 시작합니다. 그러나 Where 확장 메서드는 generic 이므로 형식 추론을 수행해야합니다.
첫 번째 인수에서 확장 메서드에 대한 불완전한 추론을 처리 할 수있는 특수 유형 추론 엔진을 작성했습니다. 유형 추론기를 실행하고를받는 Where 메서드 IEnumerable<T>
가 있고 System.Char []에서으로 추론 할 수 IEnumerable<System.Char>
있으므로 T는 System.Char입니다.
이 메서드의 Where<T>(this IEnumerable<T> items, Func<T, bool> predicate)
서명은이며 T가 System.Char임을 알고 있습니다. 또한 확장 메서드에 대한 괄호 안의 첫 번째 인수가 람다라는 것을 알고 있습니다. 그래서 우리는 "정식 매개 변수 foo는 System.Char라고 가정한다"는 람다 표현식 유형 추론기를 시작합니다. 나머지 람다를 분석 할 때이 사실을 사용합니다.
이제 "foo."인 람다의 본문을 분석하는 데 필요한 모든 정보를 얻었습니다. 우리는 foo의 유형을 찾고 람다 바인더에 따라 System.Char라는 것을 발견하고 끝났습니다. System.Char에 대한 유형 정보를 표시합니다.
그리고 키 입력 사이 의 "최상위"분석을 제외한 모든 작업을 수행 합니다. 그것은 진짜 까다로운 부분입니다. 실제로 모든 분석을 작성하는 것은 어렵지 않습니다. 그것을 만들고있다 충분히 빨리 당신이 진짜 까다로운 비트입니다 타이핑 속도로 그것을 할 수있다.
행운을 빕니다!
Delphi IDE가 Delphi 컴파일러와 함께 작동하여 IntelliSense를 수행하는 방법을 대략적으로 설명 할 수 있습니다 (코드 통찰력은 Delphi에서 호출하는 것입니다). C #에 100 % 적용 할 수는 없지만 고려해야 할 흥미로운 접근 방식입니다.
Delphi의 대부분의 의미 분석은 파서 자체에서 수행됩니다. 식은 구문 분석 될 때 형식이 지정됩니다. 단, 쉽지 않은 상황은 예외입니다.이 경우 미리보기 구문 분석을 사용하여 의도 한 내용을 파악한 다음 해당 결정이 구문 분석에 사용됩니다.
구문 분석은 연산자 우선 순위를 사용하여 구문 분석되는 표현식을 제외하고 대체로 LL (2) 재귀 하강입니다. Delphi의 뚜렷한 점 중 하나는 단일 패스 언어이므로 사용하기 전에 구성을 선언해야하므로 해당 정보를 가져 오는 데 최상위 패스가 필요하지 않습니다.
이러한 기능의 조합은 파서가 필요한 모든 지점에 대한 코드 통찰력에 필요한 대략적인 모든 정보를 가지고 있음을 의미합니다. 작동 방식은 다음과 같습니다. IDE는 컴파일러의 렉서에게 커서의 위치 (코드 통찰력이 필요한 지점)를 알리고 렉서는이를 특수 토큰 (키 비츠 토큰이라고 함)으로 변환합니다. 파서가이 토큰을 만날 때마다 (어디서나있을 수 있음) 이것은 그것이 가지고있는 모든 정보를 편집기로 되돌려 보내는 신호임을 압니다. C로 작성 되었기 때문에 longjmp를 사용하여이를 수행합니다. 그것이하는 일은 궁극적 인 호출자에게 kibitz 포인트가 발견 된 종류의 구문 구조 (즉, 문법적 문맥)와 그 포인트에 필요한 모든 기호 테이블을 통지하는 것입니다. 예를 들어 컨텍스트가 메서드에 대한 인수 인 식에있는 경우 메서드 오버로드를 확인하고 인수 형식을 살펴보고 유효한 기호를 해당 인수 형식으로 확인할 수있는 기호로만 필터링 할 수 있습니다 (이는 드롭 다운에 무관 한 잔해가 많이 있습니다). 중첩 된 범위 컨텍스트 (예 : "."뒤)에있는 경우 파서는 범위에 대한 참조를 다시 전달하고 IDE는 해당 범위에서 발견 된 모든 기호를 열거 할 수 있습니다.
다른 작업도 수행됩니다. 예를 들어, kibitz 토큰이 범위에 있지 않으면 메서드 본문을 건너 뜁니다. 이는 낙관적으로 수행되고 토큰을 건너 뛰면 롤백됩니다. 확장 메서드 (델파이의 클래스 헬퍼)와 동등한 버전의 캐시가 있으므로 조회가 상당히 빠릅니다. 그러나 Delphi의 제네릭 유형 추론은 C #보다 훨씬 약합니다.
이제 구체적인 질문에 대해 :로 선언 된 변수 유형을 추론하는 것은 var
파스칼이 상수 유형을 추론하는 방식과 동일합니다. 초기화 표현식의 유형에서 비롯됩니다. 이러한 유형은 아래에서 위로 작성됩니다. 경우 x
유형 인 Integer
및 y
유형 인 Double
다음 x + y
형식이 될 것입니다 Double
그 언어의 규칙이기 때문에; 등. 오른쪽에 전체 표현식에 대한 유형이 있고 왼쪽에있는 기호에 사용하는 유형이 될 때까지이 규칙을 따릅니다.
추상 구문 트리를 구축하기 위해 자체 파서를 작성할 필요가없는 경우 오픈 소스 인 SharpDevelop 또는 MonoDevelop 의 파서를 사용하는 방법을 살펴볼 수 있습니다.
Intellisense 시스템은 일반적으로 컴파일러와 거의 같은 방식으로 'var'변수에 할당되는 함수의 반환 유형을 확인할 수있는 추상 구문 트리를 사용하여 코드를 나타냅니다. VS Intellisense를 사용하는 경우 유효한 (해결 가능한) 할당 식 입력을 완료 할 때까지 var 유형을 제공하지 않을 수 있습니다. 식이 여전히 모호한 경우 (예 : 식에 대한 일반 인수를 완전히 추론 할 수 없음) var 유형은 확인되지 않습니다. 유형을 해결하기 위해 트리에 상당히 깊이 들어가야 할 수 있으므로 이것은 상당히 복잡한 프로세스 일 수 있습니다. 예를 들면 :
var items = myList.OfType<Foo>().Select(foo => foo.Bar);
반환 유형은 IEnumerable<Bar>
이지만이 문제를 해결하려면 다음을 알아야합니다.
- myList는
IEnumerable
. OfType<T>
IEnumerable에 적용되는 확장 메서드 가 있습니다.- 결과 값은
IEnumerable<Foo>
이고 여기Select
에 적용되는 확장 방법 이 있습니다. - 람다 식
foo => foo.Bar
에는 Foo 형식의 foo 매개 변수가 있습니다. 이것은 a를 취하는 Select의 사용법에 의해 추론되며Func<TIn,TOut>
TIn이 알려져 있기 때문에 (Foo), foo의 유형을 추론 할 수 있습니다. - Foo 유형에는 Bar 유형의 속성 Bar가 있습니다. Select 반환
IEnumerable<TOut>
및 TOut은 람다 식의 결과에서 유추 할 수 있으므로 결과 항목 유형은IEnumerable<Bar>
.
Emacs를 목표로하고 있으므로 CEDET 제품군으로 시작하는 것이 가장 좋습니다. Eric Lippert의 모든 세부 사항은 C ++ 용 CEDET / Semantic 도구의 코드 분석기에서 이미 다루었습니다. 또한 C # 파서 (약간의 TLC가 필요함)가 있으므로 누락 된 부분은 C #에 필요한 부분을 조정하는 것과 관련이 있습니다.
기본 동작은 언어별로 정의 된 오버로드 가능한 함수에 의존하는 핵심 알고리즘에서 정의됩니다. 완성 엔진의 성공 여부는 얼마나 많은 튜닝이 수행되었는지에 달려 있습니다. C ++를 가이드로 사용하면 C ++와 유사한 지원을받는 것이 그리 나쁘지 않습니다.
Daniel의 대답은 MonoDevelop을 사용하여 구문 분석 및 분석을 수행하도록 제안합니다. 이는 기존 C # 파서 대신 대체 메커니즘이거나 기존 파서를 확장하는 데 사용할 수 있습니다.
잘하는 것은 어려운 문제입니다. 기본적으로 대부분의 렉싱 / 파싱 / 타입 검사를 통해 언어 사양 / 컴파일러를 모델링하고 쿼리 할 수있는 소스 코드의 내부 모델을 빌드해야합니다. Eric은 C #에 대해 자세히 설명합니다. F # 컴파일러 소스 코드 (F # CTP의 일부)를 항상 다운로드하고 F # service.fsi
언어 서비스가 인텔리 센스, 유추 된 형식에 대한 도구 설명 등을 제공하는 데 사용하는 F # 컴파일러에서 노출 된 인터페이스를 살펴볼 수 있습니다. 호출 할 API로 컴파일러를 이미 사용할 수있는 경우 가능한 '인터페이스'에 대한 감각.
다른 방법은 설명하는 그대로 컴파일러를 재사용 한 다음 리플렉션을 사용하거나 생성 된 코드를 살펴 보는 것입니다. 이것은 컴파일러에서 컴파일 출력을 얻기 위해 '전체 프로그램'이 필요하다는 관점에서 문제가되는 반면, 편집기에서 소스 코드를 편집 할 때 아직 구문 분석하지 않은 '부분 프로그램'만있는 경우가 많습니다. 모든 방법이 아직 구현되지 않았습니다.
간단히 말해서, '저예산'버전은 잘하기가 매우 어렵고 '실제'버전은 잘하기가 매우 어렵다고 생각합니다. (여기서 '하드'는 '노력'과 '기술적 어려움'을 모두 측정합니다.)
NRefactory가이 작업 을 수행합니다.
솔루션 "1"의 경우이 작업을 빠르고 쉽게 수행 할 수있는 .NET 4의 새로운 기능이 있습니다. 따라서 프로그램을 .NET 4로 변환 할 수 있다면 최선의 선택이 될 것입니다.
'IT' 카테고리의 다른 글
확장 메소드는 동적으로 디스패치 할 수 없습니다 (0) | 2020.08.03 |
---|---|
C # String.Format () 및 String.Join ()에 해당하는 Java (0) | 2020.08.03 |
PHP에서 POST를 통해 동일한 이름을 가진 여러 입력 (0) | 2020.08.03 |
SQL이 null이고 = null (0) | 2020.08.02 |
SQL Server 2008 Windows 인증 로그인 오류 : 많은 수없는 도메인에서 로그인했습니다. (0) | 2020.08.02 |