มุมที่แปลกที่สุดที่คุณเห็นใน C # หรือ. NET คืออะไร [ปิด]


322

ฉันรวบรวมเคสมุมสองสามอันและของเล่นพัฒนาสมองและต้องการรับฟังเพิ่มเติมเสมอ หน้านี้ครอบคลุมเฉพาะบิตและบ็อตภาษา 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 โปรดยอมรับ ... )

เพื่อให้ชัดเจนนี่เป็นเพียงตัวอย่างของสิ่งที่ฉันกำลังมองหา - ฉันไม่ได้มองหาการสนทนา / คำอธิบายเรื่องประหลาดนี้เป็นพิเศษ (มันไม่เหมือนกับการฝึกงานสตริงปกติโดยเฉพาะอย่างยิ่งการฝึกสตริงจะไม่เกิดขึ้นเมื่อมีการเรียกตัวสร้าง) ฉันถามถึงพฤติกรรมแปลก ๆ

มีอัญมณีอื่น ๆ ที่ซุ่มซ่อนอยู่ที่นั่นหรือ


64
ทดสอบกับ Mono 2.0 rc; ส่งคืน True
Marc Gravell

10
สตริงทั้งสองจบลงด้วยการ string.Empty และปรากฏว่ากรอบการทำงานที่ช่วยให้เพียงหนึ่งอ้างอิงถึงที่
เอเดรีย Zanescu

34
มันเป็นสิ่งที่อนุรักษ์ความทรงจำ ค้นหาเอกสาร MSDN สำหรับสตริงวิธีการคงที่ภายใน CLR ดูแลรักษาสตริงพูล นั่นเป็นเหตุผลว่าทำไมสตริงที่มีเนื้อหาเหมือนกันปรากฏเป็นข้อมูลอ้างอิงไปยังหน่วยความจำเดียวกันนั่นคือวัตถุ
John Leidegren

12
@ John: String ฝึกงานจะเกิดขึ้นโดยอัตโนมัติสำหรับตัวอักษร นั่นไม่ใช่กรณีที่นี่ @DanielSwe: การฝึกงานไม่จำเป็นสำหรับการทำให้สตริงไม่เปลี่ยนรูป ความจริงที่เป็นไปได้คือข้อพิสูจน์ที่ดีของความไม่สามารถเปลี่ยนได้ แต่การฝึกงานตามปกติไม่ได้เกิดขึ้นที่นี่
Jon Skeet

3
รายละเอียดการใช้งานที่ทำให้เกิดพฤติกรรมนี้มีการอธิบายไว้ที่นี่: blog.liranchen.com/2010/08/brain-teasing-with-strings.html
Liran

คำตอบ:


394

ฉันคิดว่าฉันแสดงให้คุณเห็นก่อนหน้านี้ แต่ฉันชอบความสนุกที่นี่ - การแก้จุดบกพร่องบางอย่างเพื่อติดตาม! (รหัสเดิมนั้นซับซ้อนและบอบบางกว่ามาก ... )

    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());
    }

ดังนั้นสิ่งที่เ ...

คำตอบ: ใด ๆNullable<T>- int?เช่น วิธีการทั้งหมดถูกเขียนทับยกเว้น GetType () ซึ่งไม่สามารถทำได้ ดังนั้นจึงถูกส่งไปยังวัตถุ (และจากนั้นเป็น null) เพื่อเรียกใช้ object.GetType () ... ซึ่งเรียกใช้ null ;-p


Update: เนื้อเรื่องหนาขึ้น ... 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?");
}

แต่มันสามารถเอาชนะได้! การใช้ทางอ้อมเดียวกันกับสิ่งต่าง ๆ เช่น remoting; คำเตือน - ต่อไปนี้เป็นความชั่วร้ายที่บริสุทธิ์ :

class MyFunnyProxyAttribute : ProxyAttribute {
    public override MarshalByRefObject CreateInstance(Type serverType) {
        return null;
    }
}
[MyFunnyProxy]
class MyFunnyType : ContextBoundObject { }

ด้วยวิธีนี้ในสถานที่ที่new()สายจะถูกเปลี่ยนเส้นทางไปยังพร็อกซี่ ( MyFunnyProxyAttribute) nullซึ่งผลตอบแทน ตอนนี้ไปล้างตาของคุณ!


9
เหตุใดจึงไม่สามารถกำหนด Nullable <T> .GetType () ได้ ผลลัพธ์ควรเป็นประเภทของ (Nullable <T>) หรือไม่
Drew Noakes

69
Drew: ปัญหาคือ GetType () ไม่ใช่เสมือนจริงดังนั้นจึงไม่ถูกแทนที่ - ซึ่งหมายความว่าค่านั้นได้รับการบรรจุไว้สำหรับการเรียกใช้เมธอด กล่องกลายเป็นข้อมูลอ้างอิงที่ไม่มีค่าดังนั้น NRE
Jon Skeet

10
@Drew; นอกจากนี้ยังมีกฎการชกมวยแบบพิเศษสำหรับ Nullable <T> ซึ่งหมายความว่าช่องว่าง Nullable <T> เป็น null ไม่ใช่กล่องที่มีช่องว่าง Nullable <T> (และช่องว่าง null Nullable <T) >)
Marc Gravell

29
เจ๋งมาก ๆ ในทางที่ไม่บริสุทธิ์ ;-)
Konrad Rudolph

6
Constructor-constraint, 10.1.5 ใน C # 3.0 สเปคได้รับการยืนยัน
Marc Gravell

216

การปัดเศษของธนาคาร

อันนี้ไม่ได้เป็นข้อผิดพลาดของคอมไพเลอร์หรือความผิดปกติ แต่เป็นกรณีมุมแปลก ...

.Net Framework ใช้รูปแบบหรือการปัดเศษที่เรียกว่าการปัดเศษของธนาคาร

ในการปัดเศษของธนาคารตัวเลข 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


22
มันก็แปลกสำหรับฉันเช่นกัน อย่างน้อยก็จนกว่าฉันจะปัดเศษรายการตัวเลขขนาดใหญ่และคำนวณผลรวมของพวกเขา จากนั้นคุณตระหนักว่าหากคุณเพียงแค่ปัดเศษขึ้นคุณจะพบกับความแตกต่างอย่างมากจากผลรวมของตัวเลขที่ไม่ปัดเศษ แย่มากถ้าคุณกำลังทำการคำนวณทางการเงิน!
Tsvetomir Tsonev

255
ในกรณีที่คนไม่รู้คุณสามารถทำได้: Math.Round (x, MidpointRounding.AwayFromZero); เพื่อเปลี่ยนรูปแบบการปัดเศษ
ICR

26
จากเอกสาร: พฤติกรรมของวิธีนี้เป็นไปตามมาตรฐาน IEEE 754 ส่วนที่ 4 การปัดเศษชนิดนี้บางครั้งเรียกว่าการปัดเศษเป็นใกล้ที่สุดหรือปัดเศษของนายธนาคาร มันลดข้อผิดพลาดในการปัดเศษซึ่งเป็นผลมาจากการปัดเศษค่ากึ่งกลางในทิศทางเดียวอย่างสม่ำเสมอ
ICR

8
ฉันสงสัยว่านี่คือเหตุผลที่ฉันเห็นint(fVal + 0.5)บ่อยครั้งแม้ในภาษาที่มีฟังก์ชั่นการปัดเศษในตัว
Ben Blank

32
กระแทกแดกดันฉันทำงานที่ธนาคารหนึ่งครั้งและโปรแกรมเมอร์คนอื่น ๆ เริ่มพลิกออกมาเกี่ยวกับเรื่องนี้โดยคิดว่าการปัดเศษนั้นผิดปกติในกรอบการทำงาน
dan

176

ฟังก์ชั่นนี้จะทำอะไรถ้าเรียกว่าRec(0)(ไม่อยู่ภายใต้ดีบักเกอร์)

static void Rec(int i)
{
    Console.WriteLine(i);
    if (i < int.MaxValue)
    {
        Rec(i + 1);
    }
}

ตอบ:

  • บน JIT แบบ 32 บิตควรส่งผลให้ StackOverflowException
  • บน JIT 64 บิตควรพิมพ์ตัวเลขทั้งหมดไปที่ int.MaxValue

นี่เป็นเพราะคอมไพเลอร์ JIT 64- บิตใช้การปรับแต่ง tail callในขณะที่ JIT 32- บิตไม่มี

น่าเสียดายที่ฉันไม่ได้มีเครื่อง 64 บิตที่จะตรวจสอบเรื่องนี้ แต่วิธีการตรงตามเงื่อนไขทั้งหมดสำหรับการปรับให้เหมาะสมแบบหางเรียก ถ้ามีใครมีฉันสนใจที่จะดูว่ามันเป็นเรื่องจริง


10
จะต้องมีการรวบรวมในโหมดการเปิดตัว แต่ส่วนใหญ่ทำงานได้แน่นอนใน x64 =)
Neil Williams

3
อาจคุ้มค่าที่จะอัปเดตคำตอบของคุณเมื่อ VS 2010 ออกมาเนื่องจาก JIT ปัจจุบันทั้งหมดจะทำ TCO ในโหมด Release
ShuggyCoUk

3
เพิ่งลอง VS2010 Beta 1 บน WinXP 32 บิต ยังคงได้รับ StackOverflowException
squillman

130
+1 สำหรับ StackOverflowException
calvinlough

7
ว่า++มีทั้งหมดฉันโยนออก คุณโทรหาRec(i + 1)คนธรรมดาไม่ได้เหรอ?
ปรับแต่งค่าเงิน

111

มอบหมายสิ่งนี้!


นี่คือสิ่งที่ฉันต้องการถามในงานปาร์ตี้ (ซึ่งอาจเป็นสาเหตุที่ฉันไม่ได้รับเชิญอีกต่อไป):

คุณสามารถคอมไพล์โค้ดต่อไปนี้ได้หรือไม่?

    public void Foo()
    {
        this = new Teaser();
    }

โกงง่าย ๆ อาจเป็น:

string cheat = @"
    public void Foo()
    {
        this = new Teaser();
    }
";

แต่ทางออกที่แท้จริงคือ:

public struct Teaser
{
    public void Foo()
    {
        this = new Teaser();
    }
}

ดังนั้นจึงรู้เพียงเล็กน้อยว่าประเภทค่า (structs) สามารถกำหนดthisค่าตัวแปรได้อีกครั้ง


3
คลาส C ++ ก็สามารถทำได้เช่นกัน ... ตามที่ฉันค้นพบเมื่อไม่นานมานี้มี แต่จะถูกตะโกนเพราะพยายามใช้เพื่อเพิ่มประสิทธิภาพ: p
mpen

1
ฉันใช้ของจริงแทนใหม่ แค่อยากเป็นวิธีที่มีประสิทธิภาพในการอัปเดตทุกสาขา :)
mpen

70
นี่เป็นสูตรโกง: //this = new Teaser();:-)
AndrewJacksonZA

17
:-) ฉันชอบกลโกงผู้ที่อยู่ในรหัสการผลิตของฉันสิ่งที่น่ารังเกียจกว่ากำหนดใหม่นี้ ...
Omer Mor

2
จาก CLR ผ่าน C #: เหตุผลที่พวกเขาทำสิ่งนี้เป็นเพราะคุณสามารถเรียกตัวสร้างพารามิเตอร์แบบไม่มีพารามิเตอร์ของ struct ในตัวสร้างอื่น หากคุณต้องการเริ่มต้นหนึ่งค่าของ struct และต้องการให้ค่าอื่นเป็นศูนย์ / null (ค่าเริ่มต้น) คุณสามารถเขียนpublic Foo(int bar){this = new Foo(); specialVar = bar;}ได้ นี้ไม่ได้มีประสิทธิภาพและไม่เป็นธรรมจริงๆ ( specialVarกำหนดสองครั้ง) แต่เพียง FYI (นั่นคือเหตุผลที่ให้ไว้ในหนังสือเล่มนี้ฉันไม่รู้ว่าทำไมเราไม่ควรทำpublic Foo(int bar) : this())
kizzx2

100

ไม่กี่ปีที่ผ่านมาเมื่อทำงานกับโปรแกรมความภักดีเรามีปัญหากับจำนวนคะแนนที่ให้กับลูกค้า ปัญหาเกี่ยวข้องกับการแคสต์ / การแปลงสองครั้งเป็น 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 () (หรือ Math.Round ด้วย MidpointRounding ที่ตรงกับความต้องการของเรา)

int i1 = Convert.ToInt32( Math.Ceiling(d) );
int i2 = (int) Math.Ceiling(d);

44
การชี้ไปที่จำนวนเต็มไม่ได้เป็นเพียงแค่ตัดออก (มีประสิทธิภาพเสมอในการปัดเศษ) ดังนั้นนี่จึงสมเหตุสมผลดี
Max Schmeling

57
@ แม็กซ์: ใช่ แต่ทำไมแปลงรอบ?
Stefan Steinegger

18
@Stefan Steinegger หากมันได้รับการคัดเลือกแล้วมันจะไม่มีเหตุผลสำหรับมันในตอนแรกใช่มั้ย นอกจากนี้โปรดทราบว่าชื่อคลาสคือแปลงไม่ถูกส่ง
bug-a-lot

3
ใน VB: CInt () ปัดเศษ แก้ไข () ตัดทอน เผาฉันหนึ่งครั้ง ( blog.wassupy.com/2006/01/i-can-believe-it-not-truncating.html )
Michael Haren

74

พวกเขาควรจะทำให้ 0 เป็นจำนวนเต็มแม้ว่าจะมีฟังก์ชั่น enum เกินพิกัด

ฉันรู้เหตุผลของทีมแกน # สำหรับการแมป 0 ถึง enum แต่ถึงกระนั้นมันก็ไม่ได้เป็นมุมฉากเท่าที่ควร ตัวอย่างจาก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");
        }
    }
}

18
ว้าวนั่นเป็นใหม่สำหรับฉัน รวมถึงวิธีการที่ ConverTo.ToIn32 () ทำงานได้ แต่ส่งไปยัง (int) 0 ไม่ได้ และหมายเลขอื่น ๆ > 0 ใช้ได้ (โดย "ผลงาน" ผมหมายความว่าโทรเกินวัตถุ.)
ลูคัส

มีกฎการวิเคราะห์รหัสแนะนำเพื่อบังคับใช้แนวปฏิบัติที่ดีเกี่ยวกับพฤติกรรมนี้: msdn.microsoft.com/en-us/library/ms182149%28VS.80%29.aspx ลิงก์นั้นยังมีคำอธิบายที่ดีเกี่ยวกับวิธีการทำแผนที่ 0 .
Chris Clark

1
@Chris Clark: ฉันพยายามวาง None = 0 ในสัญลักษณ์ enum คอมไพเลอร์ยังคงเลือก enum สำหรับ 0 และแม้กระทั่ง (int) 0
Michael Buen

2
IMO พวกเขาควรจะแนะนำคำหลักnoneที่สามารถใช้แปลงเป็น enum ใด ๆ และทำให้ 0 เป็นค่า int และไม่สามารถแปลงเป็น enum ได้โดยปริยาย
CodesInChaos

5
ConverTo.ToIn32 () ทำงานได้เนื่องจากผลลัพธ์ไม่มีค่าคงที่แบบคอมไพล์ไทม์ และค่าคงที่คอมไพล์ไทม์ 0 เท่านั้นที่สามารถแปลงเป็น enum ใน. net รุ่นก่อนหน้าแม้แต่ตัวอักษรเท่านั้นที่0ควรแปลงเป็น enum ดูบล็อกของ Eric Lippert: blogs.msdn.com/b/ericlippert/archive/2006/03/28/563282.aspx
CodesInChaos

67

นี่เป็นหนึ่งในสิ่งที่แปลกที่สุดที่ฉันเคยเห็น (นอกเหนือจากที่นี่แน่นอน!):

public class Turtle<T> where T : Turtle<T>
{
}

มันช่วยให้คุณสามารถประกาศได้ แต่ไม่มีประโยชน์จริงเพราะมันจะขอให้คุณห่อสิ่งที่คุณต้องการให้อยู่ในชั้นเรียนพร้อมกับเต่าอีกตัวหนึ่ง

[เรื่องตลก] ฉันเดาว่ามันเป็นเต่าตลอดทางลงไป ... [/ เรื่องตลก]


34
คุณสามารถสร้างอินสแตนซ์ได้:class RealTurtle : Turtle<RealTurtle> { } RealTurtle t = new RealTurtle();
Marc Gravell

24
จริง นี่เป็นรูปแบบที่ Java enums ใช้เพื่อให้ได้ผลลัพธ์ที่ยอดเยี่ยม ฉันใช้มันใน Protocol Buffers ด้วย
Jon Skeet

6
RCIX โอ้ใช่แล้ว
Joshua

8
ฉันได้ใช้รูปแบบนี้ค่อนข้างมากในสิ่งที่เป็นข้อมูลทั่วไปแฟนซี ช่วยให้สิ่งต่าง ๆ เช่นโคลนพิมพ์อย่างถูกต้องหรือสร้างอินสแตนซ์ของตัวเอง
Lucero

20
นี่คือ 'รูปแบบเทมเพลตที่เกิดซ้ำอย่างแปลก ๆ ' en.wikipedia.org/wiki/Curiously_recurring_template_pattern
porges

65

นี่คือสิ่งที่ฉันค้นพบเมื่อไม่นานมานี้ ...

interface IFoo
{
   string Message {get;}
}
...
IFoo obj = new IFoo("abc");
Console.WriteLine(obj.Message);

ด้านบนนั้นดูบ้าคลั่งเมื่อเห็นครั้งแรก แต่จริงๆแล้วเป็นเรื่องถูกกฎหมายไม่จริง ๆ (แม้ว่าฉันจะพลาดส่วนสำคัญ แต่ก็ไม่ใช่สิ่งที่แฮ็กเช่น "เพิ่มคลาสที่เรียกว่าIFoo" หรือ "เพิ่มusingนามแฝงเพื่อชี้ไปIFooที่ ระดับ ")

ดูว่าคุณสามารถหาสาเหตุได้ทำไม: ใครบอกว่าคุณไม่สามารถสร้างอินเทอร์เฟซอินสแตนซ์ได้


1
+1 สำหรับ "โดยใช้นามแฝง" - ฉันไม่เคยรู้ว่าคุณสามารถทำที่ !
David

ตัดในคอมไพเลอร์สำหรับ COM Interop :-)
ไอออน Todirel

คุณมันเลว! อย่างน้อยคุณอาจพูดว่า "ภายใต้สถานการณ์บางอย่าง" ... คอมไพเลอร์ของฉันหักล้าง!
MA Hanin

56

เมื่อใดที่บูลีนไม่จริงหรือเท็จ?

Bill ค้นพบว่าคุณสามารถแฮ็กบูลีนเพื่อให้ถ้า A เป็นจริงและ B เป็นจริง (A และ B) เป็นเท็จ

แฮ็กบูลีน


134
เมื่อมันเป็น FILE_NOT_FOUND แน่นอน!
Greg

12
สิ่งนี้น่าสนใจเพราะมันหมายถึงการพูดทางคณิตศาสตร์ว่าไม่มีข้อความใดใน C # ที่พิสูจน์ได้ อ๊ะ
ไซมอนจอห์นสัน

20
สักวันฉันจะเขียนโปรแกรมที่ขึ้นอยู่กับพฤติกรรมนี้และปีศาจแห่งนรกที่มืดมนที่สุดจะเตรียมการต้อนรับสำหรับฉัน Bwahahahahaha!
เจฟฟรีย์ L Whitledge

18
ตัวอย่างนี้ใช้ bitwise ไม่ใช่ตัวดำเนินการเชิงตรรกะ เป็นเรื่องที่น่าแปลกใจขนาดไหน?
Josh Lee

6
เขาวางเลย์เอาต์ของโครงสร้างไว้แน่นอนว่าคุณจะได้ผลลัพธ์แปลก ๆ นั่นไม่น่าประหลาดใจหรือคาดไม่ถึง!
Ion Todirel

47

ฉันมาถึงช้าไปงานปาร์ตี้ แต่ฉันมีสาม สี่ห้า:

  1. หากคุณสำรวจ InvokeRequired บนตัวควบคุมที่ไม่ได้โหลด / แสดงมันจะพูดผิด - และระเบิดในหน้าถ้าคุณพยายามที่จะเปลี่ยนจากหัวข้ออื่น ( การแก้ปัญหาคือการอ้างอิงนี้จัดการในผู้สร้างของ ควบคุม).

  2. อีกอันหนึ่งที่ทำให้ฉันสะดุดคือการชุมนุมด้วย:

    enum MyEnum
    {
        Red,
        Blue,
    }

    ถ้าคุณคำนวณ MyEnum.Red.ToString () ในแอสเซมบลีอื่นและในระหว่างเวลาที่มีคนคอมไพล์ enum ของคุณไปที่:

    enum MyEnum
    {
        Black,
        Red,
        Blue,
    }

    ที่ runtime คุณจะได้รับ "Black"

  3. ฉันมีการชุมนุมที่ใช้ร่วมกันกับค่าคงที่ที่มีประโยชน์บางอย่างมาก่อนบรรพบุรุษของฉันปล่อยให้มีคุณสมบัติแบบ Get-Only ที่ดูน่าเกลียดฉันคิดว่าฉันจะกำจัดความยุ่งเหยิงและใช้กลุ่มสาธารณะ ฉันรู้สึกประหลาดใจเล็กน้อยเมื่อ VS รวบรวมค่าของพวกเขาและไม่ใช่การอ้างอิง

  4. ถ้าคุณใช้วิธีการใหม่ของอินเทอร์เฟซจากแอสเซมบลีอื่น แต่คุณสร้างการอ้างอิงรุ่นเก่าของแอสเซมบลีนั้นคุณจะได้รับ TypeLoadException (ไม่มีการใช้งานของ 'NewMethod') แม้ว่าคุณจะ ได้ดำเนินการ (ดูที่นี่ )

  5. พจนานุกรม <,>: "ลำดับที่จะถูกส่งคืนรายการนั้นไม่ได้กำหนด" มันน่ากลัวเพราะบางครั้งมันกัดคุณ แต่ทำงานให้คนอื่นและถ้าคุณเพิ่งเดาเอาเองว่าพจนานุกรมกำลังเล่นได้ดี ("ทำไมไม่ทำอย่างนั้นฉันคิดว่ารายการทำ") จริงๆคุณต้อง มีจมูกของคุณในมันก่อนที่คุณจะเริ่มตั้งคำถามกับสมมติฐานของคุณ


6
# 2 เป็นตัวอย่างที่น่าสนใจ Enums คือการแม็พคอมไพเลอร์ไปยังค่าอินทิกรัล ดังนั้นแม้ว่าคุณจะไม่ได้กำหนดค่าเหล่านั้นอย่างชัดเจน แต่คอมไพเลอร์ก็ส่งผลให้ MyEnum.Red = 0 และ MyEnum.Blue = 1 เมื่อคุณเพิ่ม Black คุณได้กำหนดค่า 0 ใหม่เพื่อแมปจาก Red เป็น Black ฉันสงสัยว่าปัญหาจะเกิดขึ้นในประเพณีอื่นเช่นกันเช่นการทำให้เป็นอันดับ
LBushkin

3
+1 สำหรับการเรียกใช้ที่จำเป็น ที่ของเราเราต้องการกำหนดค่าให้กับ enums อย่างชัดเจนเช่น Red = 1, Blue = 2 เพื่อให้สามารถแทรกค่าใหม่ก่อนหรือหลังจะทำให้เกิดค่าเดียวกันเสมอ จำเป็นอย่างยิ่งหากคุณกำลังบันทึกค่าลงในฐานข้อมูล
TheVillageIdiot

53
ฉันไม่เห็นด้วยที่ # 5 เป็น "ตัวพิมพ์ขอบ" พจนานุกรมไม่ควรมีลำดับที่กำหนดโดยยึดตามเมื่อคุณแทรกค่า หากคุณต้องการคำสั่งที่กำหนดให้ใช้รายการหรือใช้คีย์ที่สามารถเรียงลำดับในแบบที่เป็นประโยชน์กับคุณหรือใช้โครงสร้างข้อมูลที่แตกต่างกันโดยสิ้นเชิง
Wedge

21
@ ลิ่มเช่น SortedDictionary บางที
Allon Guralnek

4
# 3 เกิดขึ้นเนื่องจากค่าคงที่ถูกแทรกเป็นตัวอักษรทุกที่ที่ใช้ (ใน C #, อย่างน้อย) บรรพบุรุษของคุณอาจสังเกตเห็นแล้วซึ่งเป็นสาเหตุที่พวกเขาใช้คุณสมบัติรับเท่านั้น อย่างไรก็ตามตัวแปร readonly (เมื่อเทียบกับ const) จะทำงานได้เช่นกัน
Remoun

33

VB.NET, nullables และผู้ประกอบการที่ประกอบไปด้วย:

Dim i As Integer? = If(True, Nothing, 5)

การทำเช่นนี้ใช้เวลาพอสมควรในการดีบักเนื่องจากคาดว่าiจะมีNothingจะมี

ฉันมีอะไรจริงๆ 0.

สิ่งนี้น่าประหลาดใจ แต่จริง ๆ แล้วพฤติกรรม "ถูกต้อง": Nothingใน VB.NET นั้นไม่เหมือนกับnullใน CLR: Nothingอาจหมายถึงnullหรือdefault(T)ประเภทของค่าTขึ้นอยู่กับบริบท ในกรณีดังกล่าวข้างต้นIfอนุมานIntegerเป็นชนิดที่พบบ่อยของNothingและ5ดังนั้นในกรณีนี้หมายถึงNothing0


พอที่น่าสนใจผมไม่สามารถหาคำตอบนี้ดังนั้นผมจึงมีการสร้างคำถาม ใครจะรู้คำตอบอยู่ในหัวข้อนี้
GSerg

28

ฉันพบกรณีมุมแปลก ๆ ที่สองที่เต้นครั้งแรกของฉันโดยการยิงยาว

String.Equals Method (String, String, StringComparison) ไม่ได้มีผลข้างเคียงใด ๆ

ฉันกำลังทำงานกับบล็อกของรหัสที่มีสิ่งนี้ในบรรทัดด้วยตัวเองที่ด้านบนของฟังก์ชั่น:

stringvariable1.Equals(stringvariable2, StringComparison.InvariantCultureIgnoreCase);

การลบบรรทัดนั้นนำไปสู่การโอเวอร์โฟลว์สแต็กที่อื่นในโปรแกรม

รหัสกลายเป็นการติดตั้งตัวจัดการสำหรับสิ่งที่เป็นสาระสำคัญของเหตุการณ์ BeforeAssemblyLoad และพยายามทำ

if (assemblyfilename.EndsWith("someparticular.dll", StringComparison.InvariantCultureIgnoreCase))
{
    assemblyfilename = "someparticular_modified.dll";
}

โดยตอนนี้ฉันไม่ควรที่จะบอกคุณ การใช้วัฒนธรรมที่ไม่ได้ใช้มาก่อนในการเปรียบเทียบสตริงทำให้โหลดแอสเซมบลี InvariantCulture ไม่ได้เป็นข้อยกเว้น


ฉันเดาว่า "การโหลดแอสเซมบลี" เป็นผลข้างเคียงเนื่องจากคุณสามารถสังเกตได้ด้วย BeforeAssemblyLoad!
จาค็อบครัลล์

2
ว้าว. นี่เป็นช็อตที่สมบูรณ์แบบที่ขาของผู้ดูแล ฉันเดาว่าการเขียน BeforeAssemblyLoad handler สามารถนำไปสู่ความประหลาดใจมากมาย
wigy

20

นี่คือตัวอย่างของวิธีที่คุณสามารถสร้างโครงสร้างที่ทำให้เกิดข้อความแสดงข้อผิดพลาด "พยายามอ่านหรือเขียนหน่วยความจำที่ได้รับการป้องกันนี่เป็นข้อบ่งชี้ว่าหน่วยความจำอื่นเสียหาย" ความแตกต่างระหว่างความสำเร็จและความล้มเหลวนั้นลึกซึ้งมาก

การทดสอบหน่วยต่อไปนี้แสดงให้เห็นถึงปัญหา

ดูว่าคุณสามารถหาสิ่งที่ผิดพลาดได้หรือไม่

    [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);
        }
    }

หัวของฉันเจ็บ ... ทำไมมันใช้ไม่ได้
jasonh

2
หืมฉันเขียนเมื่อไม่กี่เดือนที่ผ่านมา แต่ฉันจำไม่ได้ว่าทำไมมันถึงเกิดขึ้น
cbp

10
ดูเหมือนข้อผิดพลาดของคอมไพเลอร์; การ+= 500เรียก: ldc.i4 500(กด 500 เป็น Int32) จากนั้นcall valuetype Program/MyStruct Program/MyStruct::op_Addition(valuetype Program/MyStruct, valuetype [mscorlib]System.Decimal)- ดังนั้นจึงถือว่าเป็นdecimal(96 บิต) โดยไม่มีการแปลงใด ๆ ถ้าคุณใช้+= 500Mมันทำให้ถูกต้อง ดูเหมือนว่าคอมไพเลอร์คิดว่ามันสามารถทำได้ทางเดียว (น่าจะเป็นเพราะตัวดำเนินการ int โดยนัย) จากนั้นก็ตัดสินใจที่จะทำอีกวิธีหนึ่ง
Marc Gravell

1
ขออภัยสำหรับการโพสต์สองครั้งนี่เป็นคำอธิบายที่มีคุณภาพมากขึ้น ฉันจะเพิ่มนี้ฉันได้รับบิตโดยสิ่งนี้และมัน sucks แม้ว่าฉันจะเข้าใจว่าทำไมมันเกิดขึ้น สำหรับฉันนี่เป็นข้อ จำกัด ที่โชคร้ายของโครงสร้าง / ราคา bytes.com/topic/net/answers/…
Bennett Dill

2
@Ben ได้รับข้อผิดพลาดของคอมไพเลอร์หรือการปรับเปลี่ยนที่ไม่ส่งผลกระทบต่อ struct เดิมนั้นใช้ได้ การละเมิดการเข้าถึงเป็นสัตว์ร้ายที่ค่อนข้างแตกต่างกัน รันไทม์ไม่ควรทิ้งหากคุณเพิ่งเขียนรหัสที่ปลอดภัยบริสุทธิ์จัดการ
CodesInChaos

18

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[]'

11
ตัวอย่าง IList <T> เป็นเพียงการร่ายเพราะสตริง [] ได้ใช้ ICloneable, IList, ICollection, IEnumerable, IList <string>, ICollection <string> และ IEnumerable <string>
Lucas

15

นี่เป็นสิ่งที่แปลกประหลาดที่สุดที่ฉันเคยพบโดยบังเอิญ:

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 มีข้อผิดพลาดในการโอเวอร์โหลดของ Concat ที่วัตถุถูกตรวจสอบเป็นโมฆะ แต่ไม่ใช่ผลลัพธ์ของ ToString ():

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 หากส่งคืนสตริงว่างจะถูกแทนที่objectToStringnull


3
อืม แต่ฉันสามารถจินตนาการถึงข้อผิดพลาดนี้ได้เช่นกันว่า.ToStringไม่ควรส่งคืนค่า null แต่เป็นสตริง อย่างไรก็ตามและข้อผิดพลาดในกรอบ
Dykam

12

ที่น่าสนใจ - เมื่อฉันแรกดูที่ฉันคิดว่ามันเป็นสิ่งที่คอมไพเลอร์ 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);

นอกจากนี้ยังหมายถึงtrueถ้าคุณตรวจสอบstring.Emptyซึ่งหมายความว่ารหัส op นี้จะต้องมีพฤติกรรมพิเศษในการฝึกงานสตริงว่าง


จะไม่เป็น aleck สมาร์ทหรืออะไร แต่คุณเคยได้ยินสะท้อนแสง ? มันค่อนข้างสะดวกในกรณีเหล่านี้
RCIX

3
คุณไม่ได้ฉลาด คุณไม่มีจุด - ฉันต้องการสร้าง IL เฉพาะสำหรับกรณีนี้ และอย่างไรก็ตามเนื่องจาก Reflection.Emit นั้นเป็นเรื่องเล็กน้อยสำหรับสถานการณ์ประเภทนี้มันอาจจะเร็วพอ ๆ กับการเขียนโปรแกรมใน C # จากนั้นเปิดตัวสะท้อนแสงค้นหาไบนารีการหาวิธีการ ฯลฯ ... และฉันก็ไม่จำเป็นต้อง ปล่อยให้ IDE ทำ
เกร็กบีช

10
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

ผลลัพธ์คือ "พยายามอ่านหน่วยความจำที่ได้รับการป้องกันนี่เป็นข้อบ่งชี้ว่าหน่วยความจำอื่นเสียหาย"


1
! ที่น่าสนใจ ฟังดูเหมือนบั๊กคอมไพเลอร์ ฉันย้ายไปยัง C # และใช้งานได้ดี ที่กล่าวว่ามีปัญหามากมายที่มีข้อยกเว้นที่เกิดขึ้นในการโหลดและมันทำงานแตกต่างกับ / ไม่มีดีบักเกอร์ - คุณสามารถจับกับดีบักเกอร์ได้ แต่ไม่ใช่ในบางกรณี
Marc Gravell

ขออภัยฉันลืมคุณต้องเพิ่มกล่องคำสั่งผสมไปยังแบบฟอร์มก่อนที่มันจะ
Joshua

สิ่งนี้เกี่ยวข้องกับการเริ่มต้นการโต้ตอบโดยใช้ SEH เป็นกลไกการสื่อสารภายในที่น่ากลัวหรือไม่? ฉันจำบางสิ่งบางอย่างแบบนั้นใน Win32
Daniel Earwicker

1
นี่เป็นปัญหาเดียวกัน cbp ด้านบน ประเภทค่าที่ส่งคืนคือสำเนาดังนั้นการอ้างอิงถึงคุณสมบัติใด ๆ ที่เกิดขึ้นจากสำเนาดังกล่าวจะถูกนำไปยังที่ดินบิตถัง ... bytes.com/topic/net/answers/
Bennett Dill

1
Nope ไม่มีโครงสร้างที่นี่ ฉันดีบั๊กจริง ๆ มันเพิ่ม NULL ให้กับการรวบรวมรายการในรายการของกล่องคำสั่งผสมพื้นเมืองก่อให้เกิดความผิดพลาดล่าช้า
Joshua

10

PropertyInfo.SetValue () สามารถกำหนด ints ให้กับ enums, ints เป็น nulls ได้, enums เป็น enums ที่สามารถลบล้างได้

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 !!!

คำอธิบายแบบเต็มที่นี่


10

ถ้าคุณมีคลาสทั่วไปที่มีวิธีการที่ไม่ชัดเจนขึ้นอยู่กับอาร์กิวเมนต์ชนิด? ฉันพบสถานการณ์นี้เมื่อไม่นานมานี้ในการเขียนพจนานุกรมสองทาง ฉันต้องการที่จะเขียน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และT2เหมือนกัน (และอาจเป็นถ้ามีคลาสย่อยของอีกอัน) มันเป็นข้อผิดพลาดของคอมไพเลอร์:

var r2 = new TwoWayRelationship<int, int>();
r2.Get(1);  // "The call is ambiguous..."

น่าสนใจวิธีอื่นทั้งหมดในกรณีที่สองยังคงใช้งานได้ มันเป็นเพียงการเรียกวิธีการที่ไม่ชัดเจนในตอนนี้ที่ทำให้เกิดข้อผิดพลาดของคอมไพเลอร์ กรณีที่น่าสนใจถ้าเป็นไปได้น้อยและไม่ชัดเจน


ฝ่ายตรงข้ามของวิธีการมากไปจะรักนี้ ^^
Christian Klauser

1
ฉันไม่รู้นี่มันสมเหตุสมผลกับฉันทั้งหมด
Scott Whitlock

10

C # Accessibility Puzzler


คลาสที่ได้รับต่อไปนี้คือการเข้าถึงฟิลด์ส่วนตัวจากคลาสพื้นฐานและคอมไพเลอร์มองไปทางด้านอื่น ๆ :

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;
        }
    }
}

ชั้นในจะได้รับการเข้าถึงแบบเต็มสำหรับสมาชิกชั้นนอก ในกรณีนี้ชั้นในก็เกิดขึ้นจากชั้นนอกด้วย สิ่งนี้ทำให้เราสามารถ "ทำลาย" การห่อหุ้มของสมาชิกส่วนตัว


นั่นเป็นเอกสารที่ดี msdn.microsoft.com/en-us/library/ms173120%28VS.80%29.aspx มันอาจเป็นคุณสมบัติที่มีประโยชน์ในบางครั้งโดยเฉพาะอย่างยิ่งถ้านอกคลาสคงที่

ใช่ - เป็นเอกสาร อย่างไรก็ตามมีคนน้อยมากที่ไขปริศนานี้ได้ฉันจึงคิดว่ามันเป็นเรื่องไม่สำคัญ
Omer Mor

2
ดูเหมือนว่าคุณมีความเป็นไปได้ที่แข็งแกร่งในการล้นสแต็กโดยการมีคนชั้นในสืบทอดเจ้าของ ...
Jamie Treworgy

อีกกรณีที่คล้ายกัน (และถูกต้องสมบูรณ์) กรณีที่วัตถุสามารถเข้าถึงสมาชิกส่วนตัวของวัตถุประเภทเดียวกันอื่น:class A { private int _i; public void foo(A other) { int res = other._i; } }
Olivier Jacot-Descombes

10

เพิ่งพบสิ่งเล็ก ๆ ที่ดีในวันนี้:

public class Base
{
   public virtual void Initialize(dynamic stuff) { 
   //...
   }
}
public class Derived:Base
{
   public override void Initialize(dynamic stuff) {
   base.Initialize(stuff);
   //...
   }
}

ข้อผิดพลาดนี้จะรวบรวมข้อผิดพลาด

การเรียกเมธอด 'เตรียมใช้งาน' จำเป็นต้องถูกจัดส่งแบบไดนามิก แต่ไม่สามารถเป็นเพราะเป็นส่วนหนึ่งของนิพจน์การเข้าถึงพื้นฐาน พิจารณาคัดเลือกการโต้แย้งแบบไดนามิกหรือกำจัดการเข้าถึงฐาน

ถ้าฉันเขียนฐานเริ่มต้น (สิ่งที่เป็นวัตถุ); มันทำงานได้อย่างสมบูรณ์แบบ แต่สิ่งนี้ดูเหมือนจะเป็น "คำวิเศษ" ที่นี่เนื่องจากมันเหมือนกันทุกอย่างยังคงได้รับเป็นแบบไดนามิก ...


8

ใน API ที่เราใช้วิธีการที่ส่งคืนวัตถุโดเมนอาจส่งคืน "วัตถุ null" พิเศษ ในการดำเนินการตามนี้ตัวดำเนินการเปรียบเทียบและEquals()เมธอดจะถูกแทนที่เพื่อส่งคืนtrueหากถูกเปรียบเทียบด้วยnullถ้ามันถูกเมื่อเทียบกับ

ดังนั้นผู้ใช้ API นี้อาจมีรหัสดังนี้:

return test != null ? test : GetDefault();

หรือ verbose อีกเล็กน้อยเช่นนี้

if (test == null)
    return GetDefault();
return test;

ซึ่งเป็นวิธีการที่จะกลับค่าเริ่มต้นบางอย่างที่เราต้องการที่จะใช้แทนGetDefault() nullความประหลาดใจเกิดขึ้นกับฉันเมื่อฉันใช้ ReSharper และทำตามคำแนะนำเพื่อเขียนสิ่งต่อไปนี้:

return test ?? GetDefault();

หากวัตถุทดสอบเป็นวัตถุโมฆะกลับมาจาก API แทนการที่เหมาะสมnullพฤติกรรมของรหัสที่มีการเปลี่ยนแปลงในขณะนี้เป็นผู้ประกอบการ coalescing null ตรวจสอบจริงnull, ไม่ได้ทำงานหรือoperator=Equals()


1
ไม่ใช่กรณีมุมจริงๆ แต่ท่านที่รักที่คิดอย่างนั้น!?
Ray Booysen

รหัสนี้ไม่ใช่แค่ใช้ประเภทที่เป็นโมฆะใช่ไหม ดังนั้น ReSharper แนะนำ "" ใช้. ดังที่เรย์กล่าวว่าฉันไม่คิดว่าเรื่องนี้จะเป็นมุม หรือฉันผิด
Tony

1
ใช่ประเภทเป็นโมฆะ - และนอกจากนี้ยังมี NullObject หากเป็นกรณีมุมฉันไม่รู้ แต่อย่างน้อยก็เป็นกรณีที่ 'ถ้า (a! = null) ส่งกลับค่า a; คืน b; ' ไม่เหมือนกับ 'ส่งคืนหรือไม่ b' ฉันเห็นด้วยอย่างยิ่งว่ามันเป็นปัญหากับการออกแบบเฟรมเวิร์ก / API - การโหลดมากเกินไป == null เพื่อคืนค่าจริงบนวัตถุนั้นไม่ใช่ความคิดที่ดีอย่างแน่นอน!
Tor Livar

8

พิจารณากรณีแปลก ๆ นี้:

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ที่เพิ่งจะมอบหมายการเรียกร้องให้

คอมไพเลอร์ต้องทำเช่นนี้เพื่อสนับสนุนการจัดส่ง polymorphic โดยคำนึงถึงอินเทอร์เฟซคือต้องทำให้วิธีนั้นเป็นเสมือนจริง


มันฟังดูแปลก ๆ จะต้องตรวจสอบในภายหลัง :)
Jon Skeet

@ จอน Skeet: ฉันพบนี้ขณะที่การวิจัยกลยุทธ์การดำเนินงานสำหรับบทบาทใน C # คงจะดีถ้าได้รับคำติชมจากคุณ!
Jordão

7

ต่อไปนี้อาจเป็นความรู้ทั่วไปที่ฉันเพิ่งขาดไป แต่เอ๊ะ ก่อนหน้านี้เรามีกรณีข้อบกพร่องซึ่งรวมถึงคุณสมบัติเสมือนจริง การทำให้บริบทเป็นบิตพิจารณารหัสต่อไปนี้และใช้เบรกพอยต์กับพื้นที่ที่ระบุ:

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

เอาฉันสักครู่เพื่อรับรู้ว่าเกิดอะไรขึ้น ในที่สุดฉันก็รู้แจ้งโดย Quickwatch เมื่อเข้าไปใน Quickwatch และสำรวจDerivedวัตถุ d (หรือจากบริบทของวัตถุthis) และเลือกฟิลด์baseแก้ไขที่อยู่ด้านบนของ Quickwatch จะแสดงการส่งต่อไปนี้:

((TestProject1.Base)(d))

ซึ่งหมายความว่าหากฐานถูกแทนที่เช่นการโทรจะเป็น

public string BaseProperty { get { return ((TestProject1.Base)(d)).Property; } private set { } }

สำหรับนาฬิกา Quickwatch และเคล็ดลับการเลื่อนเมาส์เพื่อแก้ไขข้อบกพร่องและจากนั้นจะทำให้การแสดงผล"AWESOME"แทน"BASE_AWESOME"เมื่อพิจารณาถึงความหลากหลาย ฉันยังไม่แน่ใจว่าทำไมมันจึงเปลี่ยนเป็นนักแสดงสมมติฐานหนึ่งคือcallอาจไม่สามารถใช้ได้จากบริบทของโมดูลเหล่านั้นและมีเพียงcallvirtอาจไม่สามารถใช้ได้ตั้งแต่บริบทโมดูลเหล่านั้นและมีเพียง

อย่างไรก็ตามที่เห็นได้ชัดว่าไม่ได้เปลี่ยนแปลงอะไรในแง่ของการทำงานDerived.BasePropertyจะยังคงกลับมาจริงๆ"BASE_AWESOME"และดังนั้นนี่ไม่ใช่รากของข้อบกพร่องของเราในที่ทำงานเพียงองค์ประกอบที่สับสน อย่างไรก็ตามฉันพบว่ามันน่าสนใจว่ามันจะสร้างความเข้าใจผิดให้กับผู้พัฒนาได้อย่างไรซึ่งจะไม่ทราบถึงความจริงนั้นในระหว่างช่วงการดีบักของพวกเขาโดยเฉพาะถ้าBaseไม่เปิดเผยในโครงการของคุณ แต่อ้างว่าเป็น DLL ของบุคคลที่สาม

"โอ้เดี๋ยวก่อนแล้วจะเป็นไงดี DLL มันเป็นแบบนั้น .. กำลังทำอะไรตลก ๆ "


ไม่มีอะไรพิเศษนั่นเป็นเพียงวิธีแทนที่การทำงาน
กำหนดค่า

7

คนนี้สวยยากไปด้านบน ฉันวิ่งเข้าไปในขณะที่ฉันพยายามสร้างการใช้งาน RealProxy ที่สนับสนุน Begin / EndInvoke อย่างแท้จริง (ขอบคุณ MS ที่ทำให้สิ่งนี้เป็นไปไม่ได้ที่จะทำโดยไม่แฮ็คที่น่ากลัว) ตัวอย่างนี้โดยทั่วไปแล้วเป็นข้อบกพร่องใน CLR เส้นทางรหัสที่ไม่มีการจัดการสำหรับ BeginInvoke ไม่ตรวจสอบว่าข้อความที่ส่งคืนจาก RealProxy.PrivateInvoke (และการแทนที่ Invoke ของฉัน) กำลังส่งคืนอินสแตนซ์ของ 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) 

6

ฉันไม่แน่ใจว่าคุณจะบอกว่านี่เป็นเรื่องแปลกของ 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


2
นี่คือการปรับปรุงความปลอดภัย (ไม่ใช่ 7, afaik) อย่างแน่นอน แต่สิ่งที่เจ๋งคือคุณสามารถอ่านและเปิดไฟล์ด้วยพา ธ ของไฟล์โปรแกรมในขณะที่ถ้าคุณดูที่นั่นด้วย explorer ก็จะไม่มีอะไรเกิดขึ้น อันนี้พาฉันไปเกือบวันทำงาน @ ลูกค้าก่อนที่ฉันจะพบมันในที่สุด
Henri

แน่นอนว่ามันก็เป็น Windows 7 เช่นกัน นั่นคือสิ่งที่ฉันใช้เมื่อฉันพบมัน ฉันเข้าใจเหตุผลที่อยู่เบื้องหลัง แต่ก็ยังน่าหงุดหงิดที่จะคิดออก
Spencer Ruport

ใน Vista / Win 7 (winXP ในทางเทคนิคด้วย) แอปควรเขียนไปยังโฟลเดอร์ AppData ในพื้นที่โฟลเดอร์ผู้ใช้เป็นข้อมูลทางเทคนิคของผู้ใช้ แอปพลิเคชันไม่ควรเขียนไปยัง programfiles / windows / system32 / etc ทุกครั้งเว้นแต่ว่าพวกเขาจะมีผู้ดูแลระบบระดับสูงและผู้ที่ได้รับรางวัลดังกล่าวควรอยู่ที่นั่นเพื่อบอกว่าอัปเกรดโปรแกรม / ถอนการติดตั้ง / ติดตั้งคุณลักษณะใหม่ แต่! ยังไม่ได้เขียนถึง system32 / windows / etc :) หากคุณเรียกใช้รหัสดังกล่าวข้างต้นในฐานะผู้ดูแลระบบ (คลิกขวา> เรียกใช้ในฐานะผู้ดูแลระบบ) ในทางทฤษฎีควรเขียนลงในโฟลเดอร์แอปไฟล์โปรแกรม
Steve Syfuhs

เสียงเหมือนการจำลองเสมือน - crispybit.spaces.live.com/blog/cns!1B71C2122AD43308!134.entry
Colin Newell

6

คุณเคยคิดว่าคอมไพเลอร์ 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 # 3.5 คอมไพเลอร์และ C # 4 คอมไพเลอร์ ...
จอนสกีต

ในระบบของฉันมันไม่ทำงาน ฉันจะวางผลลัพธ์ในคำถาม
Jordão

มันล้มเหลวสำหรับฉันใน. NET 3.5 (ไม่มีเวลาในการทดสอบ 4.0) และฉันสามารถทำซ้ำปัญหาด้วยรหัส VB.NET
Mark Hurd

3

มีบางอย่างที่น่าตื่นเต้นเกี่ยวกับ C # คือวิธีการจัดการกับการปิด

แทนที่จะทำการคัดลอกค่าตัวแปรสแต็กไปยังตัวแปรปลอดการปิดมันจะทำการห่อหุ้มตัวประมวลผลล่วงหน้าให้เกิดขึ้นทั้งหมดของตัวแปรไปยังวัตถุและย้ายมันออกจากสแต็ก - ตรงไปที่กอง! :)

ฉันเดาว่านั่นทำให้ภาษา C # สมบูรณ์ยิ่งขึ้น (หรือ lambda-complete huh) ภาษาที่ใช้งานได้ดีกว่า ML เอง (ซึ่งใช้การคัดลอกค่าสแต็ก AFAIK) F # มีคุณสมบัตินั้นเช่นกันเช่น C #

นั่นทำให้ฉันดีใจมากขอบคุณ MS พวกคุณ!

ไม่ใช่กรณีแปลก ๆ หรือมุม แต่ ... แต่มีบางอย่างที่ไม่คาดคิดจากภาษา VM ที่อิงตามสแต็กต์ :)


3

จากคำถามที่ฉันถามเมื่อไม่นานมานี้:

ผู้ประกอบการแบบมีเงื่อนไขไม่สามารถส่งโดยนัย?

ได้รับ:

Bool aBoolValue;

ที่ไหน aBoolValueถูกกำหนดจริงหรือเท็จ;

ต่อไปนี้จะไม่รวบรวม:

Byte aByteValue = aBoolValue ? 1 : 0;

แต่นี่จะ:

Int anIntValue = aBoolValue ? 1 : 0;

คำตอบที่ให้นั้นค่อนข้างดีเช่นกัน


แม้ว่าฉันve not test it Iแน่ใจว่าสิ่งนี้จะทำงาน: Byte aByteValue = aBoolValue? (Byte) 1: (Byte) 0; หรือ: Byte aByteValue = (Byte) (aBoolValue? 1: 0);
Alex Pacurar

2
ใช่อเล็กซ์ที่จะทำงาน ที่สำคัญคือในการคัดเลือกโดยปริยาย 1 : 0เพียงอย่างเดียวจะส่งไปยัง int ไม่ใช่ Byte
MPelletier

2

การกำหนดขอบเขตใน c # เป็นบางครั้งที่แปลกประหลาดอย่างแท้จริง ให้ฉันยกตัวอย่าง:

if (true)
{
   OleDbCommand command = SQLServer.CreateCommand();
}

OleDbCommand command = SQLServer.CreateCommand();

สิ่งนี้ไม่สามารถคอมไพล์ได้เนื่องจากคำสั่งถูกประกาศใหม่ มีบางอย่างที่คาดเดาสนใจว่าทำไมมันทำงานในลักษณะที่ว่าในครั้งนี้มีหัวข้อใน StackOverflowและในบล็อกของฉัน


34
ฉันไม่คิดว่าเป็นสิ่งที่แปลกประหลาด สิ่งที่คุณเรียกว่า "รหัสถูกต้องสมบูรณ์แบบ" ในบล็อกของคุณไม่ถูกต้องสมบูรณ์ตามข้อกำหนดภาษา มันอาจจะเป็นที่ถูกต้องในบางภาษาจินตนาการที่คุณต้องการเช่น C # จะเป็น แต่สเปคภาษาที่ค่อนข้างชัดเจนว่าใน C # มันไม่ถูกต้อง
Jon Skeet

7
มันใช้ได้ใน C / C ++ และเนื่องจากเป็น C # ฉันจึงอยากให้มันยังคงใช้งานได้ สิ่งที่ทำให้ฉันแย่ที่สุดคือไม่มีเหตุผลที่คอมไพเลอร์จะทำเช่นนี้ การกำหนดขอบเขตแบบซ้อนไม่ยาก ฉันเดาว่าทั้งหมดนั้นมาจากองค์ประกอบของความประหลาดใจน้อยที่สุด หมายความว่าเป็นไปได้ที่สเป็คพูดถึงสิ่งนี้และสิ่งนั้น แต่นั่นก็ไม่ได้ช่วยฉันมากนักถ้ามันไร้เหตุผลอย่างสมบูรณ์ว่ามันทำงานแบบนั้น
Anders Rune Jensen

6
C #! = C / C ++ คุณต้องการใช้เงิน << "Hello World!" << endl; แทน Console.WriteLine ("Hello World!") ;? นอกจากนี้ยังไม่ไร้เหตุผลเพียงอ่านข้อมูลจำเพาะ
Kredns

9
ฉันกำลังพูดเกี่ยวกับกฎการกำหนดขอบเขตซึ่งเป็นส่วนหนึ่งของหลักของภาษา คุณกำลังพูดเกี่ยวกับห้องสมุดมาตรฐาน แต่ตอนนี้มันชัดเจนสำหรับฉันว่าฉันควรอ่านข้อกำหนดเล็ก ๆ ของภาษา c # ก่อนที่ฉันจะเริ่มเขียนโปรแกรม
Anders Rune Jensen

6
เอริค Lippert จริงโพสต์เหตุผลที่ว่าทำไม C # ได้รับการออกแบบอย่างนั้นเมื่อเร็ว ๆ นี้: blogs.msdn.com/ericlippert/archive/2009/11/02/... สรุปเป็นเพราะมีโอกาสน้อยที่การเปลี่ยนแปลงจะมีผลที่ไม่ตั้งใจ
ช่วย
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.