C # 또는 .NET에서 가장 이상한 경우는 무엇입니까? [닫은]
나는 몇 가지 코너 케이스와 뇌 맛보기를 수집하고 항상 더 많은 것을 듣고 싶습니다. 이 페이지는 실제로 C # 언어 비트와 밥을 다루지 만 핵심 .NET 항목도 흥미 롭습니다. 예를 들어, 여기 페이지에 없지만 믿을 수없는 것을 발견했습니다.
string x = new string(new char[0]);
string y = new string(new char[0]);
Console.WriteLine(object.ReferenceEquals(x, y));
False를 인쇄 할 것으로 기대합니다. 결국 "새로운"(참조 유형의)은 항상 새로운 객체를 생성합니까? C #과 CLI 모두에 대한 사양에 따라야합니다. 글쎄,이 특별한 경우에는 그렇지 않습니다. True를 인쇄하고 테스트 한 모든 버전의 프레임 워크에서 수행했습니다. (필자는 분명히 Mono에서 시도하지 않았습니다 ...)
분명히, 이것은 내가 찾고있는 종류의 예일뿐입니다-나는이 이상한 점에 대한 토론 / 설명을 특별히 찾지 않았습니다. (일반 문자열 인턴과 동일하지 않습니다. 특히 문자열 인턴은 생성자가 호출 될 때 일반적으로 발생하지 않습니다.) 나는 비슷한 이상한 행동을 요구했습니다.
다른 보석이 숨어 있습니까?
나는 당신에게 이것을 이전에 보여줬다 고 생각하지만, 나는 여기서 재미를 좋아합니다-이것은 추적하기 위해 약간의 디버깅이 필요했습니다! (원래 코드는 분명히 더 복잡하고 미묘했습니다 ...)
static void Foo<T>() where T : new()
{
T t = new T();
Console.WriteLine(t.ToString()); // works fine
Console.WriteLine(t.GetHashCode()); // works fine
Console.WriteLine(t.Equals(t)); // works fine
// so it looks like an object and smells like an object...
// but this throws a NullReferenceException...
Console.WriteLine(t.GetType());
}
그래서 T는 ...
답변 : 어떤 Nullable<T>
- 등 int?
. GetType ()을 제외한 모든 메서드는 재정의됩니다. 따라서 null을 호출하는 object.GetType () ...을 호출하기 위해 객체로 캐스팅 (따라서 null로)됩니다.
업데이트 : 줄거리가 두껍게 ... Ayende Rahien은 그의 블로그 에서 비슷한 도전을 던졌지 만 where T : class, new()
:
private static void Main() {
CanThisHappen<MyFunnyType>();
}
public static void CanThisHappen<T>() where T : class, new() {
var instance = new T(); // new() on a ref-type; should be non-null, then
Debug.Assert(instance != null, "How did we break the CLR?");
}
그러나 이길 수 있습니다! 원격과 같은 것들에 의해 사용 된 것과 같은 간접적 사용; 경고-다음은 순수한 악입니다 .
class MyFunnyProxyAttribute : ProxyAttribute {
public override MarshalByRefObject CreateInstance(Type serverType) {
return null;
}
}
[MyFunnyProxy]
class MyFunnyType : ContextBoundObject { }
이를 사용하면 new()
호출이 프록시 ( MyFunnyProxyAttribute
)로 리디렉션되어을 반환합니다 null
. 자 가서 눈을 씻으십시오!
은행가의 반올림.
이것은 컴파일러 버그 나 오작동이 아니라 이상한 코너 케이스입니다 ...
.Net Framework는 Banker 's Rounding이라는 스키마 또는 반올림을 사용합니다.
은행원 반올림에서 0.5 숫자는 가장 가까운 짝수로 반올림되므로
Math.Round(-0.5) == 0
Math.Round(0.5) == 0
Math.Round(1.5) == 2
Math.Round(2.5) == 2
etc...
이로 인해 잘 알려진 Round-Half-Up 반올림을 기반으로 재무 계산에서 예기치 않은 버그가 발생할 수 있습니다.
이것은 Visual Basic에서도 마찬가지입니다.
이 함수는 Rec(0)
(디버거가 아닌)으로 호출되면 어떻게됩니까?
static void Rec(int i)
{
Console.WriteLine(i);
if (i < int.MaxValue)
{
Rec(i + 1);
}
}
대답:
- 32 비트 JIT에서는 StackOverflowException이 발생합니다.
- 64 비트 JIT에서는 모든 숫자를 int.MaxValue에 인쇄해야합니다.
때문입니다 64 비트 JIT 컴파일러는 꼬리 호출 최적화를 적용 32 비트 JIT하지 않는 반면,.
불행히도 이것을 확인할 수있는 64 비트 머신이 없지만 메소드는 테일 콜 최적화를위한 모든 조건을 충족합니다. 아무도 가지고 있지 않다면 나는 그것이 사실인지 알고 싶습니다.
이것을 할당하십시오!
이것은 내가 파티에서 묻고 싶은 것입니다 (아마도 더 이상 초대받지 않는 이유 일 것입니다).
다음 코드를 컴파일 할 수 있습니까?
public void Foo()
{
this = new Teaser();
}
쉬운 속임수는 다음과 같습니다.
string cheat = @"
public void Foo()
{
this = new Teaser();
}
";
그러나 실제 해결책은 다음과 같습니다.
public struct Teaser
{
public void Foo()
{
this = new Teaser();
}
}
따라서 값 유형 (구조체)이 this
변수를 재 할당 할 수 있다는 사실은 알지 못합니다 .
몇 년 전, 충성도 프로그램을 진행할 때 고객에게 제공되는 포인트의 양에 문제가있었습니다. 이 문제는 double을 int로 캐스팅 / 변환하는 것과 관련이 있습니다.
아래 코드에서 :
double d = 13.6;
int i1 = Convert.ToInt32(d);
int i2 = (int)d;
i1 == i2 입니까?
i1! = i2로 밝혀졌습니다. 변환 및 캐스트 연산자에서 다른 반올림 정책으로 인해 실제 값은 다음과 같습니다.
i1 == 14
i2 == 13
Math.Ceiling () 또는 Math.Floor () (또는 요구 사항을 충족하는 MidpointRounding을 사용하는 Math.Round)를 호출하는 것이 항상 좋습니다.
int i1 = Convert.ToInt32( Math.Ceiling(d) );
int i2 = (int) Math.Ceiling(d);
열거 형 함수 과부하가 있어도 0을 정수로 만들어야합니다.
0을 열거 형에 매핑하는 C # 핵심 팀의 이론적 근거를 알고 있었지만 여전히 직각이 아닙니다. Npgsql의 예제 .
테스트 예 :
namespace Craft
{
enum Symbol { Alpha = 1, Beta = 2, Gamma = 3, Delta = 4 };
class Mate
{
static void Main(string[] args)
{
JustTest(Symbol.Alpha); // enum
JustTest(0); // why enum
JustTest((int)0); // why still enum
int i = 0;
JustTest(Convert.ToInt32(0)); // have to use Convert.ToInt32 to convince the compiler to make the call site use the object version
JustTest(i); // it's ok from down here and below
JustTest(1);
JustTest("string");
JustTest(Guid.NewGuid());
JustTest(new DataTable());
Console.ReadLine();
}
static void JustTest(Symbol a)
{
Console.WriteLine("Enum");
}
static void JustTest(object o)
{
Console.WriteLine("Object");
}
}
}
이것은 내가 지금까지 본 것 중 가장 특이한 것 중 하나입니다 (물론 여기에서 제외하십시오!) :
public class Turtle<T> where T : Turtle<T>
{
}
그것은 당신이 그것을 선언 할 수는 있지만 실제로 사용하지는 않습니다. 왜냐하면 그것은 항상 당신이 센터에있는 클래스를 다른 거북으로 포장하도록 요구하기 때문입니다.
[농담] 거북이가 끝까지 내려간 것 같아 ... [/ 농담]
최근에 알게 된 것 중 하나입니다 ...
interface IFoo
{
string Message {get;}
}
...
IFoo obj = new IFoo("abc");
Console.WriteLine(obj.Message);
위의 내용은 언뜻보기에는 미쳤지 만 실제로는 합법적입니다 . 아니요 , 실제로 (핵심 부분을 놓쳤지만 "클래스 추가 "또는 " 별칭을 추가하여 별명 추가" 와 같은 해킹 은 아닙니다 . 수업").IFoo
using
IFoo
이유를 알아낼 수 있는지 확인하십시오. 누가 인터페이스를 인스턴스화 할 수 없다고 말합니까?
부울이 True 또는 False가 아닌 경우는 언제입니까?
Bill은 부울을 해킹하여 A가 True이고 B가 True 인 경우 (A 및 B)가 False임을 발견했습니다.
나는 파티에 조금 늦게 도착하고 있지만
3
4
5가 있습니다.
로드 / 표시되지 않은 컨트롤에서 InvokeRequired를 폴링하면 false로 표시되고 다른 스레드에서 변경하려고하면 얼굴이 날려 버립니다 ( 해결 방법 은 이것을 참조하는 것입니다). 제어).
나를 놀라게 한 또 다른 하나는 다음과 같은 어셈블리가 주어진 것입니다.
enum MyEnum { Red, Blue, }
다른 어셈블리에서 MyEnum.Red.ToString ()을 계산하고 때로는 누군가가 열거 형을 다음과 같이 컴파일했습니다.
enum MyEnum { Black, Red, Blue, }
런타임에 "검정색"이 표시됩니다.
나는 편리한 상수를 가진 공유 어셈블리를 가지고 있었다. 나의 전임자는 추악하게 보이는 get-only 속성을 남겨두고 혼란을 없애고 public const를 사용한다고 생각했다. VS가 참조가 아닌 값으로 컴파일했을 때 조금 놀랐습니다.
다른 어셈블리에서 인터페이스의 새 메서드를 구현하지만 해당 어셈블리의 이전 버전을 참조하여 다시 빌드하는 경우 구현 한 경우에도 TypeLoadException ( 'NewMethod'구현 없음) 이 발생 합니다 ( 여기 참조 ).
Dictionary <,> : "항목이 반환되는 순서는 정의되어 있지 않습니다". 이것은 때때로 당신을 물 수 있지만 다른 사람들을 일할 수 있기 때문에 끔찍 합니다. 사전이 훌륭하게 재생 될 것이라고 맹목적으로 가정했다면 ( "왜 안됩니까? 나는 생각했습니다, 목록은 생각합니다"), 당신은 정말로해야합니다. 마침내 당신의 가정에 의문을 제기하기 전에 코를 넣으십시오.
VB.NET, 널 입력 가능 및 삼항 연산자 :
Dim i As Integer? = If(True, Nothing, 5)
i
포함하기를 기대했기 때문에 디버깅하는 데 시간이 걸렸습니다 Nothing
.
내가 실제로 무엇을 포함합니까? 0
.
이것은 놀랍지 만 실제로 "올바른"동작입니다. Nothing
VB.NET의 null
CLR 과 정확히 동일하지는 않습니다 . 컨텍스트에 따라 또는 type type을 Nothing
의미 할 수 있습니다 . 위의 경우, 추론한다 공통의 종류로 하고 있으므로,이 경우, 방법 .null
default(T)
T
If
Integer
Nothing
5
Nothing
0
장거리 촬영으로 첫 번째를 능가하는 두 번째 이상한 코너 케이스를 발견했습니다.
String.Equals 메서드 (String, String, StringComparison)는 실제로 부작용이 없습니다.
나는 일부 기능의 맨 위에 자체적으로 이것을 가지고있는 코드 블록을 작업하고있었습니다.
stringvariable1.Equals(stringvariable2, StringComparison.InvariantCultureIgnoreCase);
해당 줄을 제거하면 프로그램의 다른 곳에서 스택 오버플로가 발생합니다.
이 코드는 본질적으로 BeforeAssemblyLoad 이벤트에 대한 핸들러를 설치하고 시도하는 것으로 밝혀졌습니다.
if (assemblyfilename.EndsWith("someparticular.dll", StringComparison.InvariantCultureIgnoreCase))
{
assemblyfilename = "someparticular_modified.dll";
}
지금은 말할 필요가 없습니다. 문자열 비교에서 이전에 사용되지 않은 문화권을 사용하면 어셈블리로드가 발생합니다. InvariantCulture는 이에 대한 예외가 아닙니다.
다음은 "보호 된 메모리를 읽거나 쓰려고 시도했습니다. 이것은 종종 다른 메모리가 손상되었다는 표시"오류 메시지를 발생시키는 구조체를 만드는 방법의 예입니다. 성공과 실패의 차이는 매우 미묘합니다.
다음 단위 테스트는 문제를 보여줍니다.
무엇이 잘못되었는지 알아낼 수 있는지보십시오.
[Test]
public void Test()
{
var bar = new MyClass
{
Foo = 500
};
bar.Foo += 500;
Assert.That(bar.Foo.Value.Amount, Is.EqualTo(1000));
}
private class MyClass
{
public MyStruct? Foo { get; set; }
}
private struct MyStruct
{
public decimal Amount { get; private set; }
public MyStruct(decimal amount) : this()
{
Amount = amount;
}
public static MyStruct operator +(MyStruct x, MyStruct y)
{
return new MyStruct(x.Amount + y.Amount);
}
public static MyStruct operator +(MyStruct x, decimal y)
{
return new MyStruct(x.Amount + y);
}
public static implicit operator MyStruct(int value)
{
return new MyStruct(value);
}
public static implicit operator MyStruct(decimal value)
{
return new MyStruct(value);
}
}
C #은 배열이 다차원 적이 지 않고 형식간에 상속 관계가 있고 형식이 참조 형식 인 경우 배열과 목록 간의 변환을 지원합니다.
object[] oArray = new string[] { "one", "two", "three" };
string[] sArray = (string[])oArray;
// Also works for IList (and IEnumerable, ICollection)
IList<string> sList = (IList<string>)oArray;
IList<object> oList = new string[] { "one", "two", "three" };
이것은 작동하지 않습니다.
object[] oArray2 = new int[] { 1, 2, 3 }; // Error: Cannot implicitly convert type 'int[]' to 'object[]'
int[] iArray = (int[])oArray2; // Error: Cannot convert type 'object[]' to 'int[]'
이것은 내가 우연히 만난 가장 이상한 것입니다.
public class DummyObject
{
public override string ToString()
{
return null;
}
}
다음과 같이 사용됩니다 :
DummyObject obj = new DummyObject();
Console.WriteLine("The text: " + obj.GetType() + " is " + obj);
던질 것 NullReferenceException
입니다. C # 컴파일러가 여러 개의 추가를 호출하여에 대한 컴파일을 수행합니다 String.Concat(object[])
. .NET 4 이전에는 객체가 null인지 검사하지만 ToString ()의 결과가 아닌 Concat의 과부하에 버그가 있습니다.
object obj2 = args[i];
string text = (obj2 != null) ? obj2.ToString() : string.Empty;
// if obj2 is non-null, but obj2.ToString() returns null, then text==null
int length = text.Length;
이것은 ECMA-334 §14.7.4의 버그입니다.
이항 + 연산자는 피연산자 중 하나 또는 모두가 유형일 때 문자열 연결을 수행합니다
string
. 문자열 연결 피연산자가null
인 경우 빈 문자열이 대체됩니다. 그렇지 않으면, 문자열이 아닌 피연산자는ToString
type에서 상속 된 가상 메소드 를 호출하여 문자열 표현으로 변환됩니다object
. 을ToString
반환null
하면 빈 문자열이 대체됩니다.
흥미롭게도-처음 살펴봤을 때 C # 컴파일러가 확인하고있는 것으로 가정했지만 간섭 가능성을 제거하기 위해 직접 IL을 방출하더라도 여전히 발생합니다. 즉, 실제로 작동하는 newobj
op 코드입니다. 확인 중.
var method = new DynamicMethod("Test", null, null);
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Newarr, typeof(char));
il.Emit(OpCodes.Newobj, typeof(string).GetConstructor(new[] { typeof(char[]) }));
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Newarr, typeof(char));
il.Emit(OpCodes.Newobj, typeof(string).GetConstructor(new[] { typeof(char[]) }));
il.Emit(OpCodes.Call, typeof(object).GetMethod("ReferenceEquals"));
il.Emit(OpCodes.Box, typeof(bool));
il.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new[] { typeof(object) }));
il.Emit(OpCodes.Ret);
method.Invoke(null, null);
또한 이 op 코드가 빈 문자열을 인턴하기 위해 특별한 동작을 가져야한다는 것을 true
확인 하는 경우 와 같습니다 string.Empty
.
Public Class Item
Public ID As Guid
Public Text As String
Public Sub New(ByVal id As Guid, ByVal name As String)
Me.ID = id
Me.Text = name
End Sub
End Class
Public Sub Load(sender As Object, e As EventArgs) Handles Me.Load
Dim box As New ComboBox
Me.Controls.Add(box) 'Sorry I forgot this line the first time.'
Dim h As IntPtr = box.Handle 'Im not sure you need this but you might.'
Try
box.Items.Add(New Item(Guid.Empty, Nothing))
Catch ex As Exception
MsgBox(ex.ToString())
End Try
End Sub
"보호 된 메모리를 읽으려고했습니다. 다른 메모리가 손상되었음을 나타냅니다."
PropertyInfo.SetValue ()는 정수를 열거 형에, 정수를 nullable 정수에, 열거 형을 nullable 열거 형에 할당 할 수 있지만 int는 nullable 열거 형에 할당 할 수 없습니다.
enumProperty.SetValue(obj, 1, null); //works
nullableIntProperty.SetValue(obj, 1, null); //works
nullableEnumProperty.SetValue(obj, MyEnum.Foo, null); //works
nullableEnumProperty.SetValue(obj, 1, null); // throws an exception !!!
전체 설명은 여기
형식 인수에 따라 모호하게 만들 수있는 메서드가있는 일반 클래스가 있다면 어떻습니까? 나는 최근 양방향 사전을 작성 하여이 상황에 부딪쳤다. Get()
전달 된 인수의 반대를 반환하는 대칭 메소드 를 작성하고 싶었습니다 . 이 같은:
class TwoWayRelationship<T1, T2>
{
public T2 Get(T1 key) { /* ... */ }
public T1 Get(T2 key) { /* ... */ }
}
당신이 인스턴스를 만들 경우 경우 모든 잘 좋은 T1
및 T2
다른 유형이 있습니다 :
var r1 = new TwoWayRelationship<int, string>();
r1.Get(1);
r1.Get("a");
그러나 T1
and T2
가 같으면 (아마도 하나가 다른 것의 서브 클래스 인 경우) 컴파일러 오류입니다.
var r2 = new TwoWayRelationship<int, int>();
r2.Get(1); // "The call is ambiguous..."
흥미롭게도, 두 번째 경우의 다른 모든 방법은 여전히 사용 가능합니다. 컴파일러 오류를 일으키는 모호한 메소드 만 호출합니다. 흥미롭지 않은 경우, 조금 어려워도 모호합니다.
C # 접근성 퍼즐
다음 파생 클래스는 기본 클래스에서 개인 필드에 액세스 하고 컴파일러는 다른 쪽을 자동으로 찾습니다.
public class Derived : Base
{
public int BrokenAccess()
{
return base.m_basePrivateField;
}
}
이 필드는 실제로 비공개입니다.
private int m_basePrivateField = 0;
그러한 코드를 어떻게 컴파일 할 수 있을까?
.
.
.
.
.
.
.
대답
속임수는 Derived
내부 클래스 로 선언 하는 것입니다 Base
.
public class Base
{
private int m_basePrivateField = 0;
public class Derived : Base
{
public int BrokenAccess()
{
return base.m_basePrivateField;
}
}
}
내부 클래스는 외부 클래스 멤버에게 완전히 액세스 할 수 있습니다. 이 경우 내부 클래스는 외부 클래스에서도 파생됩니다. 이를 통해 개인 구성원의 캡슐화를 "중단"할 수 있습니다.
오늘 좋은 작은 것을 발견했습니다.
public class Base
{
public virtual void Initialize(dynamic stuff) {
//...
}
}
public class Derived:Base
{
public override void Initialize(dynamic stuff) {
base.Initialize(stuff);
//...
}
}
컴파일 오류가 발생합니다.
'Initialize'메소드에 대한 호출은 동적으로 디스패치되어야하지만 기본 액세스 표현식의 일부이기 때문에 불가능합니다. 동적 인수를 캐스트하거나 기본 액세스를 제거하십시오.
base.Initialize (객체로 물건)를 쓰면; 그것은 완벽하게 작동하지만, 이것은 정확히 "동일한 단어이기 때문에 모든 것이 여전히 역동적으로 받아 들여지기 때문에 여기서"마법의 단어 "인 것 같습니다
우리가 사용하는 API에서 도메인 객체를 반환하는 메소드는 특별한 "널 객체"를 반환 할 수 있습니다. 이를 구현할 때 비교 연산자와 Equals()
메소드가 와 비교 true
되면 리턴되도록 대체됩니다 null
.
따라서이 API 사용자에게는 다음과 같은 코드가있을 수 있습니다.
return test != null ? test : GetDefault();
또는 다음과 같이 조금 더 장황합니다.
if (test == null)
return GetDefault();
return test;
where GetDefault()
대신에 사용하려는 일부 기본값을 반환하는 메소드가 있습니다 null
. ReSharper를 사용할 때 놀랍게도 다음 중 하나를 다시 작성하는 것이 좋습니다.
return test ?? GetDefault();
테스트 객체가 적절한 대신 API에서 반환 된 null 객체 인 null
경우 null 통합 연산자가 실제로 또는 null
실행 중이 아닌을 검사하므로 코드 동작이 변경되었습니다 .operator=
Equals()
이 이상한 경우를 고려하십시오.
public interface MyInterface {
void Method();
}
public class Base {
public void Method() { }
}
public class Derived : Base, MyInterface { }
Base
와 Derived
동일한 어셈블리에서 선언 된 경우 컴파일러는 인터페이스를 구현하지 Base::Method
않더라도 CIL에서 가상 및 봉인 Base
됩니다.
경우 Base
와 Derived
다른 어셈블리에있는을 컴파일 할 때, Derived
조립이에 멤버를 소개합니다, 그래서 컴파일러는, 다른 어셈블리를 변경하지 않습니다 Derived
에 대한 명시 적으로 구현 될 것 MyInterface::Method
그건 그냥 호출을 위임합니다 Base::Method
.
컴파일러는 인터페이스와 관련하여 다형성 디스패치를 지원하기 위해이 작업을 수행해야합니다. 즉, 해당 메서드를 가상으로 만들어야합니다.
다음은 제가 단순히 부족한 일반적인 지식 일 수 있습니다. 얼마 전, 가상 속성을 포함한 버그 사례가있었습니다. 컨텍스트를 약간 추상화하고 다음 코드를 고려하여 지정된 영역에 중단 점을 적용하십시오.
class Program
{
static void Main(string[] args)
{
Derived d = new Derived();
d.Property = "AWESOME";
}
}
class Base
{
string _baseProp;
public virtual string Property
{
get
{
return "BASE_" + _baseProp;
}
set
{
_baseProp = value;
//do work with the base property which might
//not be exposed to derived types
//here
Console.Out.WriteLine("_baseProp is BASE_" + value.ToString());
}
}
}
class Derived : Base
{
string _prop;
public override string Property
{
get { return _prop; }
set
{
_prop = value;
base.Property = value;
} //<- put a breakpoint here then mouse over BaseProperty,
// and then mouse over the base.Property call inside it.
}
public string BaseProperty { get { return base.Property; } private set { } }
}
에있는 동안 Derived
추가 할 때 개체 컨텍스트, 같은 동작을 얻을 수있는 base.Property
시계, 또는 입력으로 base.Property
간략한에.
무슨 일이 있었는지 깨달았어요 결국 나는 퀵 워치에 의해 깨달았습니다. Quickwatch로 이동하여 Derived
객체 d (또는 객체의 컨텍스트에서 this
)를 탐색하고 필드를 선택 base
하면 Quickwatch 상단의 편집 필드에 다음 캐스트가 표시됩니다.
((TestProject1.Base)(d))
즉,베이스가 이와 같이 교체되면 통화는
public string BaseProperty { get { return ((TestProject1.Base)(d)).Property; } private set { } }
Watches, Quickwatch 및 디버깅 마우스 오버 툴팁에 대해 다형성을 고려할 때 "AWESOME"
대신 표시하는 것이 좋습니다 "BASE_AWESOME"
. 나는 그것이 왜 그것을 캐스트로 변환 할 것인지 확실하지 call
않습니다. 하나의 가설은 해당 모듈의 컨텍스트에서 사용 가능하지 않을 수 있다는 것 callvirt
입니다.
어쨌든, 기능면에서 아무것도 변경하지 않는 것은 Derived.BaseProperty
여전히 실제로 반환 될 것입니다. "BASE_AWESOME"
따라서 이것은 직장에서의 버그의 근본이 아니며 단순히 혼란스러운 구성 요소입니다. 그러나 디버그 세션 중에 해당 사실을 알지 못하는 개발자를 어떻게 잘못 인도 할 수 있는지 흥미 롭습니다. 특히 Base
프로젝트에 노출되지 않고 타사 DLL로 참조되어 Dev가 다음과 같이 말한 경우 :
"Oi, wait..what? omg that DLL like, ..doing something funny"
이건 꽤 어렵습니다. Begin / EndInvoke를 실제로 지원하는 RealProxy 구현을 구축하려고 시도하는 동안 문제가 발생했습니다 (MS는 끔찍한 해킹없이 수행 할 수 없기 때문에 감사합니다). 이 예제는 기본적으로 CLR의 버그이며 BeginInvoke의 관리되지 않는 코드 경로는 RealProxy.PrivateInvoke (및 내 호출 재정의)의 반환 메시지가 IAsyncResult의 인스턴스를 반환하는지 확인하지 않습니다. 그것이 반환되면, CLR은 놀랍게도 혼란스러워지고 하단의 테스트에서 입증 된 것처럼 무슨 일이 일어나고 있는지 전혀 알지 못합니다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Proxies;
using System.Reflection;
using System.Runtime.Remoting.Messaging;
namespace BrokenProxy
{
class NotAnIAsyncResult
{
public string SomeProperty { get; set; }
}
class BrokenProxy : RealProxy
{
private void HackFlags()
{
var flagsField = typeof(RealProxy).GetField("_flags", BindingFlags.NonPublic | BindingFlags.Instance);
int val = (int)flagsField.GetValue(this);
val |= 1; // 1 = RemotingProxy, check out System.Runtime.Remoting.Proxies.RealProxyFlags
flagsField.SetValue(this, val);
}
public BrokenProxy(Type t)
: base(t)
{
HackFlags();
}
public override IMessage Invoke(IMessage msg)
{
var naiar = new NotAnIAsyncResult();
naiar.SomeProperty = "o noes";
return new ReturnMessage(naiar, null, 0, null, (IMethodCallMessage)msg);
}
}
interface IRandomInterface
{
int DoSomething();
}
class Program
{
static void Main(string[] args)
{
BrokenProxy bp = new BrokenProxy(typeof(IRandomInterface));
var instance = (IRandomInterface)bp.GetTransparentProxy();
Func<int> doSomethingDelegate = instance.DoSomething;
IAsyncResult notAnIAsyncResult = doSomethingDelegate.BeginInvoke(null, null);
var interfaces = notAnIAsyncResult.GetType().GetInterfaces();
Console.WriteLine(!interfaces.Any() ? "No interfaces on notAnIAsyncResult" : "Interfaces");
Console.WriteLine(notAnIAsyncResult is IAsyncResult); // Should be false, is it?!
Console.WriteLine(((NotAnIAsyncResult)notAnIAsyncResult).SomeProperty);
Console.WriteLine(((IAsyncResult)notAnIAsyncResult).IsCompleted); // No way this works.
}
}
}
산출:
No interfaces on notAnIAsyncResult
True
o noes
Unhandled Exception: System.EntryPointNotFoundException: Entry point was not found.
at System.IAsyncResult.get_IsCompleted()
at BrokenProxy.Program.Main(String[] args)
이것이 Windows Vista / 7 이상한지 또는 .Net 이상한지 확실하지 않지만 잠시 동안 내 머리를 긁적였습니다.
string filename = @"c:\program files\my folder\test.txt";
System.IO.File.WriteAllText(filename, "Hello world.");
bool exists = System.IO.File.Exists(filename); // returns true;
string text = System.IO.File.ReadAllText(filename); // Returns "Hello world."
Windows Vista / 7에서 파일은 실제로 C:\Users\<username>\Virtual Store\Program Files\my folder\test.txt
C # 컴파일러가 잘못된 CIL을 생성 할 수 있다고 생각한 적이 있습니까? 이것을 실행하면 TypeLoadException
:
interface I<T> {
T M(T p);
}
abstract class A<T> : I<T> {
public abstract T M(T p);
}
abstract class B<T> : A<T>, I<int> {
public override T M(T p) { return p; }
public int M(int p) { return p * 2; }
}
class C : B<int> { }
class Program {
static void Main(string[] args) {
Console.WriteLine(new C().M(42));
}
}
그래도 C # 4.0 컴파일러에서 어떻게 작동하는지 모르겠습니다.
편집 : 이것은 내 시스템의 출력입니다.
C:\Temp>type Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1 {
interface I<T> {
T M(T p);
}
abstract class A<T> : I<T> {
public abstract T M(T p);
}
abstract class B<T> : A<T>, I<int> {
public override T M(T p) { return p; }
public int M(int p) { return p * 2; }
}
class C : B<int> { }
class Program {
static void Main(string[] args) {
Console.WriteLine(new C().M(11));
}
}
}
C:\Temp>csc Program.cs
Microsoft (R) Visual C# 2008 Compiler version 3.5.30729.1
for Microsoft (R) .NET Framework version 3.5
Copyright (C) Microsoft Corporation. All rights reserved.
C:\Temp>Program
Unhandled Exception: System.TypeLoadException: Could not load type 'ConsoleAppli
cation1.C' from assembly 'Program, Version=0.0.0.0, Culture=neutral, PublicKeyTo
ken=null'.
at ConsoleApplication1.Program.Main(String[] args)
C:\Temp>peverify Program.exe
Microsoft (R) .NET Framework PE Verifier. Version 3.5.30729.1
Copyright (c) Microsoft Corporation. All rights reserved.
[token 0x02000005] Type load failed.
[IL]: Error: [C:\Temp\Program.exe : ConsoleApplication1.Program::Main][offset 0x
00000001] Unable to resolve token.
2 Error(s) Verifying Program.exe
C:\Temp>ver
Microsoft Windows XP [Version 5.1.2600]
클로저를 처리하는 방식 인 C #에는 정말 흥미로운 점이 있습니다.
스택 변수 값을 클로저 프리 변수에 복사하는 대신, 전 처리기 매직은 변수의 모든 발생을 객체로 래핑하여 스택에서 힙으로 바로 이동시킵니다! :)
ML # (AFAIK를 사용하는 스택 값 복사를 사용하는)보다 C #이 기능적으로 완전한 (또는 람다 완료 된) 언어를 훨씬 더 많이 만듭니다. C #과 마찬가지로 F #에도 해당 기능이 있습니다.
그것은 저에게 많은 즐거움을 가져다줍니다. MS 여러분 감사합니다!
이상한 점이나 코너 케이스는 아니지만 스택 기반 VM 언어에서 실제로 예상치 못한 것입니다. :)
얼마 전에 내가 물었던 질문에서 :
주어진:
Bool aBoolValue;
어디에 aBoolValue
True 또는 False가 할당됩니다.
다음은 컴파일되지 않습니다 :
Byte aByteValue = aBoolValue ? 1 : 0;
그러나 이것은 :
Int anIntValue = aBoolValue ? 1 : 0;
제공된 답변도 꽤 좋습니다.
C #의 범위는 때때로 기괴합니다. 한 가지 예를 들어 보겠습니다.
if (true)
{
OleDbCommand command = SQLServer.CreateCommand();
}
OleDbCommand command = SQLServer.CreateCommand();
명령이 다시 선언 되었기 때문에 컴파일에 실패합니까? 왜이 스레드에서 stackoverflow 및 내 블로그 에서 그렇게 작동하는지에 대한 흥미로운 추측이 있습니다.
'Programing' 카테고리의 다른 글
이름으로 쿠키 받기 (0) | 2020.03.06 |
---|---|
문자열 목록을 초기화하는 방법 (목록 (0) | 2020.03.06 |
Android SDK 빌드 도구, 플랫폼 도구 및 도구 란 무엇입니까? (0) | 2020.03.06 |
REST 웹 애플리케이션의 페이지 매김 (0) | 2020.03.06 |
로깅 모범 사례 (0) | 2020.03.06 |