gotcha ที่เลวร้ายที่สุดใน C # หรือ. NET คืออะไร? [ปิด]


377

ฉันเพิ่งทำงานกับDateTimeวัตถุและเขียนสิ่งนี้:

DateTime dt = DateTime.Now;
dt.AddDays(1);
return dt; // still today's date! WTF?

เอกสาร Intellisense สำหรับAddDays()บอกว่าจะเพิ่มวันในวันที่ซึ่งไม่ได้ - จริง ๆ แล้วมันคืนวันที่มีวันที่เพิ่มเข้าไปดังนั้นคุณต้องเขียนมันเช่น:

DateTime dt = DateTime.Now;
dt = dt.AddDays(1);
return dt; // tomorrow's date

อันนี้กัดฉันหลายครั้งก่อนดังนั้นฉันคิดว่ามันจะมีประโยชน์ในการแคตตาล็อก C # gotchas ที่เลวร้ายที่สุด


157
ส่งกลับ DateTime ตอนนี้เพิ่มวัน (1);
crashmstr

23
AFAIK ชนิดของค่าในตัวนั้นไม่เปลี่ยนรูปทั้งหมดอย่างน้อยก็ในวิธีการใด ๆ ที่รวมอยู่ในประเภทนั้นจะคืนค่ารายการใหม่แทนที่จะแก้ไขรายการที่มีอยู่ อย่างน้อยฉันก็ไม่สามารถคิดออกจากหัวของฉันที่ไม่ได้ทำสิ่งนี้: ทั้งหมดที่ดีและสอดคล้อง
Joel Coehoorn

6
ประเภทค่าที่ไม่แน่นอน: System.Collections.Generics.List.Enumerator :( (และใช่คุณจะเห็นว่ามันมีพฤติกรรมแปลก ๆ ถ้าคุณพยายามอย่างหนักพอ)
Jon Skeet

13
IntelliSense ให้ข้อมูลทั้งหมดที่คุณต้องการ มันบอกว่ามันจะส่งคืนวัตถุ DateTime ถ้ามันเพิ่งเปลี่ยนสิ่งที่คุณส่งผ่านมันจะเป็นวิธีการโมฆะ
John Kraft

20
ไม่จำเป็นต้อง: StringBuilder.Append (... ) ส่งคืน "this" เช่น ค่อนข้างพบได้ทั่วไปในส่วนต่อประสานที่คล่องแคล่ว
Jon Skeet

คำตอบ:


304
private int myVar;
public int MyVar
{
    get { return MyVar; }
}

Blammo แอปของคุณล่มโดยไม่มีการติดตามสแต็ก เกิดขึ้นตลอดเวลา

(ให้สังเกตเมืองหลวงMyVarแทนตัวพิมพ์เล็กmyVarในช่องว่าง)


112
และเพื่อให้เหมาะสมสำหรับเว็บไซต์นี้ :)
gbjbaanb

62
ฉันใส่ขีดกับสมาชิกส่วนตัวช่วยได้มาก!
chakrit

61
ฉันใช้คุณสมบัติอัตโนมัติที่ฉันสามารถทำได้หยุดปัญหาประเภทนี้ได้มาก)
TWith2Sugars

28
นี่คือเหตุผลที่ดีในการใช้คำนำหน้าสำหรับเขตข้อมูลส่วนตัวของคุณ (มีคนอื่น ๆ แต่นี่เป็นสิ่งที่ดี): _myVar, m_myVar
jrista

205
@jrista: O โปรดไม่ ... ไม่ ... m_ aargh สยองขวัญ ...
fretje

254

Type.GetType

Type.GetType(string)หนึ่งซึ่งผมเคยเห็นจำนวนมากกัดของคนเป็น พวกเขาสงสัยว่าทำไมมันทำงานประเภทในการชุมนุมของตัวเองและบางชนิดชอบแต่ไม่ได้System.String System.Windows.Forms.Formคำตอบก็คือว่ามันเพียง mscorlibแต่ดูในปัจจุบันการประกอบและใน


วิธีการที่ไม่ระบุชื่อ

C # 2.0 แนะนำวิธีการที่ไม่ระบุชื่อนำไปสู่สถานการณ์ที่น่ารังเกียจเช่นนี้:

using System;
using System.Threading;

class Test
{
    static void Main()
    {
        for (int i=0; i < 10; i++)
        {
            ThreadStart ts = delegate { Console.WriteLine(i); };
            new Thread(ts).Start();
        }
    }
}

สิ่งที่จะพิมพ์ออกมา? มันขึ้นอยู่กับการตั้งเวลา มันจะพิมพ์ตัวเลข 10 แต่มันอาจจะไม่พิมพ์ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ซึ่งเป็นสิ่งที่คุณคาดหวัง ปัญหาคือมันเป็นiตัวแปรที่ถูกดักจับไม่ใช่ค่า ณ จุดที่สร้างผู้รับมอบสิทธิ์ สิ่งนี้สามารถแก้ไขได้อย่างง่ายดายด้วยตัวแปรพิเศษในขอบเขตที่เหมาะสม:

using System;
using System.Threading;

class Test
{
    static void Main()
    {
        for (int i=0; i < 10; i++)
        {
            int copy = i;
            ThreadStart ts = delegate { Console.WriteLine(copy); };
            new Thread(ts).Start();
        }
    }
}

การดำเนินการบล็อกตัววนซ้ำที่เลื่อนออกไป

"การทดสอบหน่วยของชายยากจน" นี้ไม่ผ่าน - ทำไมไม่

using System;
using System.Collections.Generic;
using System.Diagnostics;

class Test
{
    static IEnumerable<char> CapitalLetters(string input)
    {
        if (input == null)
        {
            throw new ArgumentNullException(input);
        }
        foreach (char c in input)
        {
            yield return char.ToUpper(c);
        }
    }

    static void Main()
    {
        // Test that null input is handled correctly
        try
        {
            CapitalLetters(null);
            Console.WriteLine("An exception should have been thrown!");
        }
        catch (ArgumentNullException)
        {
            // Expected
        }
    }
}

คำตอบคือรหัสภายในซอร์สโค้ดของCapitalLettersโค้ดไม่ได้ถูกดำเนินการจนกว่าMoveNext()จะเรียกเมธอดiterator เป็นครั้งแรก

ฉันมีความแปลกประหลาดอื่น ๆ บางบนของหน้า brainteasers


25
ตัวอย่างตัววนซ้ำเป็นสิ่งที่คดเคี้ยว!
Jimmy

8
ทำไมไม่แบ่งคำตอบนี้เป็น 3 คำตอบเพื่อให้เราสามารถลงคะแนนแต่ละรายการแทนกันทั้งหมด
chakrit

13
@ จักรกฤษ: เมื่อมองย้อนกลับไปอาจจะเป็นความคิดที่ดี แต่ฉันคิดว่ามันสายเกินไปแล้ว มันอาจดูเหมือนว่าฉันกำลังพยายามหาตัวแทนมากขึ้น ...
Jon Skeet

19
Type.GetType จริง ๆ แล้วทำงานถ้าคุณระบุ AssemblyQualifiedName Type.GetType ("System.ServiceModel.EndpointNotFoundException, System.ServiceModel, เวอร์ชัน = 3.0.0.0, วัฒนธรรม = เป็นกลาง, PublicKeyToken = b77a5c561934e089");
chilltemp

2
@ kentaromiura: การแก้ปัญหาการโอเวอร์โหลดเริ่มต้นที่ชนิดที่ได้มามากที่สุดและทำงานบนต้นไม้ - แต่ดูเฉพาะวิธีการที่ประกาศไว้ในประเภทที่กำลังมองหา Foo (int) แทนที่เมธอด base ดังนั้นจะไม่ถูกพิจารณา สามารถใช้ Foo (วัตถุ) ได้ดังนั้นการแก้ปัญหาการโอเวอร์โหลดจะหยุดลงที่นั่น ฉันรู้
Jon Skeet

194

โยนข้อยกเว้นอีกครั้ง

gotcha ที่ได้รับนักพัฒนาใหม่ ๆ จำนวนมากคือซีแมนทิกส์ re-Throw exception

มีเวลามากมายที่ฉันเห็นโค้ดดังนี้

catch(Exception e) 
{
   // Do stuff 
   throw e; 
}

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

รหัสที่ถูกต้องเป็นทั้งคำสั่ง throw โดยไม่มี args:

catch(Exception)
{
    throw;
}

หรือล้อมรอบข้อยกเว้นในอีกอันหนึ่งและใช้การยกเว้นภายในเพื่อรับการติดตามสแต็กดั้งเดิม:

catch(Exception e) 
{
   // Do stuff 
   throw new MySpecialException(e); 
}

โชคดีมากที่ฉันได้รับการสอนเกี่ยวกับเรื่องนี้ในสัปดาห์แรกของฉันโดยใครบางคนและพบว่ามันอยู่ในรหัสนักพัฒนาอาวุโสมากขึ้น Is: catch () {throw; } เหมือนกับโค้ดที่สองใช่ไหม catch (Exception e) {throw; } เพียง แต่มันไม่ได้สร้างวัตถุข้อยกเว้นและเติมมัน?
StuperUser

นอกเหนือจากข้อผิดพลาดของการใช้ throw ex (หรือ Throw e) แทนที่จะเพิ่งโยนฉันต้องสงสัยว่ามีกรณีใดบ้างที่มีข้อยกเว้นที่จะต้องทำการโยนอีกครั้ง
Ryan Lundy

13
@Kyralessa: มีหลายกรณีเช่นถ้าคุณต้องการย้อนกลับธุรกรรมก่อนที่ผู้เรียกจะได้รับการยกเว้น คุณย้อนกลับแล้วหมุนใหม่
R. Martinho Fernandes

7
ฉันเห็นสิ่งนี้ตลอดเวลาที่ผู้คนจับและรื้อฟื้นข้อยกเว้นเพียงเพราะพวกเขาได้รับการสอนว่าพวกเขาต้องจับข้อยกเว้นทั้งหมดโดยไม่ทราบว่ามันจะถูกดักจับสแต็กการโทรต่อไป มันทำให้ฉันบ้า
James Westgate

5
@Kyralessa กรณีที่ใหญ่ที่สุดคือเมื่อคุณต้องทำการบันทึก เข้าสู่ระบบผิดพลาดในการจับและ rethrow ..
Nawfal

194

หน้าต่างนาฬิกาของไฮเซนเบิร์ก

สิ่งนี้สามารถกัดคุณอย่างรุนแรงหากคุณทำสิ่งที่ต้องการโหลดเช่นนี้:

private MyClass _myObj;
public MyClass MyObj {
  get {
    if (_myObj == null)
      _myObj = CreateMyObj(); // some other code to create my object
    return _myObj;
  }
}

สมมติว่าคุณมีรหัสอื่นที่ใช้สิ่งนี้:

// blah
// blah
MyObj.DoStuff(); // Line 3
// blah

ตอนนี้คุณต้องการที่จะแก้ปัญหาCreateMyObj()วิธีการของคุณ ดังนั้นคุณจึงใส่เบรกพอยต์ในบรรทัดที่ 3 ด้านบนด้วยความตั้งใจที่จะก้าวเข้าไปในรหัส สำหรับการวัดที่ดีคุณสามารถใส่เบรกพอยต์บนบรรทัดด้านบนที่ระบุ_myObj = CreateMyObj();และแม้แต่เบรกพอยต์ภายในCreateMyObj()ตัวเอง

รหัสจะถึงจุดพักของคุณในบรรทัดที่ 3 คุณก้าวเข้าสู่โค้ด คุณคาดว่าจะป้อนรหัสตามเงื่อนไขเพราะ_myObjเห็นได้ชัดว่าเป็นโมฆะใช่ไหม? เอ่อ ... ดังนั้น ... ทำไมมันข้ามเงื่อนไขและตรงไปที่return _myObj! คุณวางเมาส์ไว้เหนือ _myObj ... และแน่นอนว่ามันมีค่า! มันเกิดขึ้นได้อย่างไร!

คำตอบก็คือ IDE ของคุณทำให้เกิดค่าเนื่องจากคุณมีหน้าต่าง "เฝ้าดู" เปิดอยู่ - โดยเฉพาะหน้าต่างเฝ้าดู "รถยนต์" ซึ่งแสดงค่าของตัวแปร / คุณสมบัติทั้งหมดที่เกี่ยวข้องกับบรรทัดการทำงานปัจจุบันหรือก่อนหน้า เมื่อคุณกดเบรกพอยต์ของคุณบนบรรทัดที่ 3 หน้าต่างนาฬิกาตัดสินใจว่าคุณจะสนใจที่จะรู้ถึงคุณค่าของMyObj- ดังนั้นเบื้องหลังการเพิกเฉยจุดพักของคุณมันไปและคำนวณค่าของMyObjคุณ - รวมถึงการโทรไปCreateMyObj()ที่ กำหนดค่าของ _myObj!

นั่นเป็นเหตุผลที่ฉันเรียกสิ่งนี้ว่าหน้าต่าง Heisenberg Watch - คุณไม่สามารถสังเกตเห็นคุณค่าโดยไม่ส่งผลกระทบต่อมัน ... :)

Gotcha!


แก้ไข - ฉันรู้สึกว่าความคิดเห็นของ @ ChristianHayter สมควรได้รับการรวมไว้ในคำตอบหลักเนื่องจากดูเหมือนว่าวิธีแก้ไขปัญหาที่มีประสิทธิภาพสำหรับปัญหานี้ ดังนั้นเมื่อใดก็ตามที่คุณมีทรัพย์สินที่ขี้เกียจ ...

ตกแต่งคุณสมบัติของคุณด้วย [DebuggerBrowsable (DebuggerBrowsableState.Never)] หรือ [DebuggerDisplay ("<โหลดตามต้องการ>")] - คริสเตียนเฮเตอร์


10
ค้นหาที่ยอดเยี่ยม! คุณไม่ใช่โปรแกรมเมอร์คุณเป็นนักแก้จุดบกพร่องที่แท้จริง
สิ่งนี้ __curious_geek

26
ฉันพบเจอสิ่งนี้แม้กระทั่งวางเมาส์เหนือตัวแปรไม่ใช่แค่ในหน้าต่างดู
Richard Morgan

31
ตกแต่งสถานที่ให้บริการของคุณด้วยหรือ[DebuggerBrowsable(DebuggerBrowsableState.Never)] [DebuggerDisplay("<loaded on demand>")]
Christian Hayter

4
ถ้าคุณกำลังพัฒนาคลาสเฟรมเวิร์กและต้องการฟังก์ชั่นหน้าต่างการเฝ้าดูโดยไม่เปลี่ยนพฤติกรรมของรันไทม์ของคุณสมบัติที่สร้างขึ้นอย่างเกียจคร้านคุณสามารถใช้พร็อกซีชนิดดีบักเกอร์เพื่อส่งคืนค่าถ้ามันถูกสร้างขึ้นแล้วและข้อความที่คุณสมบัติไม่ได้ ถูกสร้างขึ้นหากเป็นกรณีนี้ Lazy<T>ชั้น (โดยเฉพาะอย่างยิ่งสำหรับValueทรัพย์สิน) เป็นตัวอย่างหนึ่งของที่นี้จะใช้
Sam Harwell

4
ผมจำได้ว่าคนที่ (ด้วยเหตุผลบางอย่างที่ฉันไม่เข้าใจสามารถ) ToStringการเปลี่ยนแปลงมูลค่าของวัตถุในการโอเวอร์โหลดของ ทุกครั้งที่เขาวนเวียนอยู่เหนือคำแนะนำเครื่องมือนั้นให้คุณค่าที่แตกต่าง - เขาไม่สามารถเข้าใจได้ ...
JNF

144

นี่คืออีกครั้งที่ทำให้ฉัน:

static void PrintHowLong(DateTime a, DateTime b)
{
    TimeSpan span = a - b;
    Console.WriteLine(span.Seconds);        // WRONG!
    Console.WriteLine(span.TotalSeconds);   // RIGHT!
}

TimeSpan.Secondsเป็นส่วนวินาทีของช่วงเวลา (2 นาทีและ 0 วินาทีมีค่าวินาทีเป็น 0)

TimeSpan.TotalSecondsเป็นช่วงเวลาทั้งหมดที่วัดเป็นวินาที (2 นาทีมีค่าวินาทีทั้งหมด 120)


1
ใช่แล้วมีฉันด้วย ฉันคิดว่ามันควรจะเป็น TimeSpan.SecondsPart หรือบางสิ่งบางอย่างเพื่อให้ชัดเจนยิ่งขึ้นว่ามันหมายถึงอะไร
Dan Diplo

3
เกี่ยวกับเรื่องการอ่านนี้ฉันต้องสงสัยว่าทำไมTimeSpanแม้มีSecondsทรัพย์สินที่มีทั้งหมด ใครให้ลาของหนูว่าส่วนวินาทีของช่วงเวลาคืออะไรกันล่ะ? มันเป็นค่าที่ขึ้นกับหน่วย ฉันไม่สามารถเข้าใจถึงการใช้งานจริงใด ๆ สำหรับมัน
MusiGenesis

2
ทำให้รู้สึกถึงฉันว่า TimeSpan.TotalSeconds จะกลับมา ... จำนวนวินาทีทั้งหมดในช่วงเวลา
Ed S.

16
@MusiGenesis คุณสมบัตินี้มีประโยชน์ ถ้าฉันต้องการแสดงไทม์แพนแยกเป็นชิ้น ๆ เช่นสมมติว่า Timespan ของคุณแสดงระยะเวลา '3 ชั่วโมง 15 นาที 10 วินาที' คุณสามารถเข้าถึงข้อมูลนี้โดยไม่มีคุณสมบัติวินาที, ชั่วโมง, นาทีได้อย่างไร
SolutionYogi

1
ใน API ที่คล้ายกันฉันใช้SecondsPartและSecondsTotalแยกแยะทั้งสอง
BlueRaja - Danny Pflughoeft

80

หน่วยความจำรั่วเนื่องจากคุณไม่ได้ยกเลิกการเชื่อมต่อเหตุการณ์

สิ่งนี้ยิ่งทำให้นักพัฒนาอาวุโสบางคนที่ฉันรู้จักทราบ

ลองนึกภาพรูปแบบ WPF ที่มีหลายสิ่งในนั้นและที่ใดที่หนึ่งที่นั่นคุณสมัครรับกิจกรรม หากคุณไม่ได้ยกเลิกการเป็นสมาชิกแบบฟอร์มทั้งหมดจะถูกเก็บไว้ในหน่วยความจำหลังจากที่ถูกปิดและยกเลิกการอ้างอิง

ฉันเชื่อว่าปัญหาที่ฉันเห็นคือการสร้าง DispatchTimer ในรูปแบบ WPF และสมัครสมาชิกเหตุการณ์ Tick ถ้าคุณไม่ทำ - = ในตัวจับเวลาที่ฟอร์มของคุณรั่วหน่วยความจำ!

ในตัวอย่างนี้โค้ดการฉีกขาดของคุณควรมี

timer.Tick -= TimerTickEventHandler;

อันนี้เป็นเรื่องยากโดยเฉพาะอย่างยิ่งเมื่อคุณสร้างอินสแตนซ์ของ DispatchTimer ภายในรูปแบบ WPF ดังนั้นคุณจะคิดว่ามันจะเป็นการอ้างอิงภายในที่จัดการโดยกระบวนการรวบรวมขยะ ... น่าเสียดายที่ DispatchTimer ใช้รายการภายในแบบคงที่ของการสมัครสมาชิกและบริการ คำร้องขอบนเธรด UI ดังนั้นการอ้างอิงคือ 'เป็นเจ้าของ' โดยคลาสสแตติก


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

3
มีคำแนะนำการเชื่อมต่อ MS สำหรับเหตุการณ์อ้างอิงอ่อนที่นี่ซึ่งจะแก้ปัญหานี้ได้ แต่ในความคิดของฉันเราควรเปลี่ยนรูปแบบเหตุการณ์ที่ไม่น่าเชื่อทั้งหมดด้วยแบบคู่ที่อ่อนแออย่างเช่นที่ใช้โดย CAB
BlueRaja - Danny Pflughoeft

+1 จากฉันขอบคุณ! ไม่เป็นไรขอบคุณสำหรับงานตรวจสอบโค้ดที่ฉันต้องทำ!
Bob Denny

@ BlueRaja-DannyPflughoeft ด้วยเหตุการณ์ที่อ่อนแอคุณมี gotcha อื่น - คุณไม่สามารถสมัคร lambdas ได้ คุณไม่สามารถเขียนtimer.Tick += (s, e,) => { Console.WriteLine(s); }
Ark-kun

@ Ark-kun ใช่แลมบ์ดาทำให้มันยากขึ้นคุณจะต้องบันทึกแลมบ์ดาของคุณไว้ในตัวแปรแล้วใช้มันในโค้ดการฉีกขาดของคุณ ค่อนข้างจะทำลายความเรียบง่ายของการเขียน lambdas ใช่ไหม?
Timothy Walters

63

อาจไม่ใช่ gotcha จริง ๆ เพราะพฤติกรรมนั้นเขียนไว้อย่างชัดเจนใน MSDN แต่คอของฉันเสียหนึ่งครั้งเพราะฉันพบว่าค่อนข้างตอบโต้:

Image image = System.Drawing.Image.FromFile("nice.pic");

ผู้ชายคนนี้ออกจาก"nice.pic"ไฟล์ที่ถูกล็อคจนกว่าภาพจะถูกกำจัด ในเวลาที่ฉันเผชิญหน้ากับมันฉันก็คงจะดีใจที่ได้โหลดไอคอนได้ทันทีและไม่รู้ตัว (ตอนแรก) ว่าฉันจบลงด้วยการเปิดและล็อคไฟล์มากมาย! รูปภาพติดตามตำแหน่งที่โหลดไฟล์จาก ...

วิธีแก้ปัญหานี้ ฉันคิดว่าสายการบินเดียวจะทำงานได้ ฉันคาดว่าจะมีพารามิเตอร์เพิ่มเติมสำหรับFromFile()แต่ไม่มีเลยฉันจึงเขียนสิ่งนี้ ...

using (Stream fs = new FileStream("nice.pic", FileMode.Open, FileAccess.Read))
{
    image = System.Drawing.Image.FromStream(fs);
}

10
ฉันยอมรับว่าพฤติกรรมนี้ไม่สมเหตุสมผล ฉันไม่พบคำอธิบายใด ๆ นอกเหนือจาก "พฤติกรรมนี้เกิดจากการออกแบบ"
MusiGenesis

1
โอ้และสิ่งที่ยอดเยี่ยมเกี่ยวกับวิธีแก้ปัญหานี้คือถ้าคุณพยายามโทรหา Image.ToStream (ฉันลืมชื่อที่แน่นอนจากมือ) ในภายหลังมันจะไม่ทำงาน
Joshua

55
ต้องตรวจสอบรหัส brb
Esben Skov Pedersen

7
@EsbenSkovPedersen ช่างเป็นความคิดเห็นที่เรียบง่าย แต่ตลกและแห้ง ทำวันของฉัน
Inisheer

51

หากคุณนับ ASP.NET ฉันจะบอกว่าวงจรชีวิตของเว็บฟอร์มนั้นเป็น gotcha ที่ยอดเยี่ยมสำหรับฉัน ฉันใช้เวลานับไม่ถ้วนในการดีบักโค้ดเว็บฟอร์มที่เขียนไม่ดีเพียงเพราะนักพัฒนาจำนวนมากไม่เข้าใจว่าจะใช้ตัวจัดการเหตุการณ์เมื่อใด (รวมอยู่ด้วยเศร้า)


26
นั่นเป็นเหตุผลที่ผมย้ายไป MVC ... ปวดหัว viewstate ...
ชาคริต

29
มีคำถามอื่นอีกทั้งหมดที่อุทิศให้กับ ASP.NET gotchas (สมควรจะได้) แนวคิดพื้นฐานของ ASP.NET (การทำให้เว็บแอปดูเหมือนว่าแอพ windows สำหรับนักพัฒนา) นั้นถูกเข้าใจผิดอย่างน่ากลัวจนฉันไม่แน่ใจว่ามันจะนับว่าเป็น "gotcha"
MusiGenesis

1
MusiGenesis ฉันหวังว่าฉันจะโหวตความคิดเห็นของคุณได้ร้อยครั้ง
csauve

3
@MusiGenesis ดูเหมือนว่าจะเข้าใจผิดในขณะนี้ แต่ในเวลานั้นผู้คนต้องการเว็บแอปพลิเคชันของพวกเขา (แอปพลิเคชันเป็นคำสำคัญ - ASP.NET WebForms ไม่ได้ออกแบบมาเพื่อโฮสต์บล็อก) เพื่อทำงานเหมือนกับแอปพลิเคชัน windows สิ่งนี้เปลี่ยนไปค่อนข้างเร็ว ๆ นี้และผู้คนจำนวนมากยังคง "ไม่ค่อยมี" ปัญหาทั้งหมดคือสิ่งที่เป็นนามธรรมรั่วไหลเกินไป - เว็บไม่ได้ทำงานเหมือนแอปพลิเคชันเดสก์ท็อปมากจนนำไปสู่ความสับสนในเกือบทุกคน
Luaan

1
สิ่งแรกที่ฉันเคยเห็นเกี่ยวกับ ASP.NET ก็คือกระแทกแดกดันจากวิดีโอของ Microsoft ที่แสดงให้เห็นว่าคุณสามารถสร้างเว็บไซต์บล็อกได้อย่างง่ายดายโดยใช้ ASP.NET!
MusiGenesis

51

โอเวอร์โหลด == ตัวดำเนินการและคอนเทนเนอร์ที่ไม่ได้พิมพ์ (รายการอาร์เรย์, ชุดข้อมูล ฯลฯ ):

string my = "my ";
Debug.Assert(my+"string" == "my string"); //true

var a = new ArrayList();
a.Add(my+"string");
a.Add("my string");

// uses ==(object) instead of ==(string)
Debug.Assert(a[1] == "my string"); // true, due to interning magic
Debug.Assert(a[0] == "my string"); // false

การแก้ปัญหา?

  • ใช้เสมอstring.Equals(a, b)เมื่อคุณเปรียบเทียบชนิดสตริง

  • การใช้ข้อมูลทั่วไปต้องการList<string>ให้แน่ใจว่าตัวถูกดำเนินการทั้งสองเป็นสตริง


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

1
แอ๊กชั่น! คุณพูดถูก :) ตกลงฉันแก้ไขนิดหน่อย
Jimmy

มีการสร้างคำเตือนเกี่ยวกับการใช้งานดังกล่าว
chakrit

11
ใช่หนึ่งในข้อบกพร่องที่ใหญ่ที่สุดที่มีภาษา C # คือตัวดำเนินการ == ใน Object Object พวกเขาควรบังคับให้เราใช้ ReferenceEquals
erikkallen

2
โชคดีตั้งแต่ 2.0 เรามียาชื่อสามัญ ไม่ต้องกังวลถ้าคุณใช้ List <string> ในตัวอย่างด้านบนแทนที่จะเป็น ArrayList รวมทั้งเราได้รับประสิทธิภาพจากมันใช่! ฉันมักจะทำการอ้างอิงเก่า ๆ ไปยัง ArrayLists ในรหัสดั้งเดิมของเรา
JoelC

48
[Serializable]
class Hello
{
    readonly object accountsLock = new object();
}

//Do stuff to deserialize Hello with BinaryFormatter
//and now... accountsLock == null ;)

คุณธรรมของเรื่องราว: ผู้เริ่มต้นฟิลด์จะไม่ทำงานเมื่อยกเลิกการปรับวัตถุ


8
ใช่ฉันเกลียด. การทำให้เป็นอันดับ. NET ไม่ได้เรียกใช้ตัวสร้างค่าเริ่มต้น ฉันหวังว่ามันจะเป็นไปไม่ได้ที่จะสร้างวัตถุโดยไม่เรียกสิ่งก่อสร้างใด ๆ แต่ก็ไม่ใช่
Roman Starkov

45

DateTime.ToString ("dd / MM / yyyy") ; สิ่งนี้จะไม่ให้ dd / MM / yyyy เสมอไป แต่จะคำนึงถึงการตั้งค่าภูมิภาคและแทนที่ตัวคั่นวันที่ของคุณขึ้นอยู่กับว่าคุณอยู่ที่ไหน ดังนั้นคุณอาจได้ dd-MM-yyyy หรืออะไรทำนองนั้น

วิธีที่เหมาะสมในการทำเช่นนี้คือใช้DateTime.ToString ("dd '/' MM '/' yyyy");


DateTime.ToString ("r")ควรจะแปลงเป็น RFC1123 ซึ่งใช้ GMT GMT อยู่ภายในเสี้ยววินาทีจาก UTC และตัวระบุรูปแบบ "r" จะไม่แปลงเป็น UTCแม้ว่า DateTime ที่เป็นปัญหาจะถูกระบุเป็น Local

ซึ่งจะส่งผลให้ gotcha ต่อไปนี้ (แตกต่างกันไปขึ้นอยู่กับเวลาท้องถิ่นของคุณจาก UTC)

DateTime.Parse("Tue, 06 Sep 2011 16:35:12 GMT").ToString("r")
>              "Tue, 06 Sep 2011 17:35:12 GMT"

อ๊ะ!


19
เปลี่ยน mm เป็น MM - mm คือนาทีและ MM คือเดือน gotcha อีกผมคิดว่า ...
Kobi

1
ฉันสามารถดูว่านี่จะเป็น gotcha ได้อย่างไรถ้าคุณไม่รู้ (ฉันไม่ได้) ... แต่ฉันพยายามคิดออกเมื่อคุณต้องการพฤติกรรมที่คุณพยายามพิมพ์วันที่โดยเฉพาะ ไม่ตรงกับการตั้งค่าภูมิภาคของคุณ
Beska

6
@Beska: เนื่องจากคุณกำลังเขียนไฟล์จึงจำเป็นต้องอยู่ในรูปแบบที่กำหนดพร้อมกับรูปแบบวันที่ที่ระบุ
GvS

11
ฉันเห็นว่าค่าเริ่มต้นที่แปลเป็นภาษาท้องถิ่นนั้นแย่กว่าวิธีอื่น ๆ อย่างน้อยนักพัฒนาก็ไม่สนใจการแปลอย่างสมบูรณ์รหัสทำงานบนเครื่องที่มีการแปลที่แตกต่างกัน ด้วยวิธีนี้รหัสอาจไม่ทำงาน
Joshua

32
จริง ๆ แล้วฉันเชื่อว่าวิธีที่ถูกต้องในการทำเช่นนี้คือDateTime.ToString("dd/MM/yyyy", CultureInfo.InvariantCulture);
BlueRaja - Danny Pflughoeft

44

ฉันเห็นสิ่งนี้โพสต์เมื่อวันก่อนและฉันคิดว่ามันค่อนข้างคลุมเครือและเจ็บปวดสำหรับผู้ที่ไม่รู้

int x = 0;
x = x++;
return x;

ตามที่จะส่งคืน 0 และไม่ใช่ 1 อย่างที่คาดไว้


37
ฉันหวังว่าจะไม่กัดคนจริง ๆ - ฉันหวังว่าพวกเขาจะไม่เขียนมันตั้งแต่แรก! (มันน่าสนใจอยู่แล้วแน่นอน)
Jon Skeet

12
ผมไม่คิดว่านี่เป็นที่คลุมเครือมาก ...
คริส Marasti-Georg

10
อย่างน้อยใน C # ผลลัพธ์จะถูกกำหนดถ้าไม่คาดคิด ใน C ++ อาจเป็น 0 หรือ 1 หรือผลลัพธ์อื่น ๆ รวมถึงการยกเลิกโปรแกรม!
James Curran

7
นี่ไม่ใช่ gotcha; x = x ++ -> x = x จากนั้นเพิ่ม x .... x = ++ x -> เพิ่ม x จากนั้น x = x
Kevin

28
@ เควิน: ฉันไม่คิดว่ามันค่อนข้างง่าย ถ้า x = x ++ เท่ากับ x = x ตามด้วย x ++ ผลลัพธ์จะเป็น x = 1 แต่ฉันคิดว่าสิ่งที่เกิดขึ้นคืออันดับแรกนิพจน์ทางด้านขวาของเครื่องหมายเท่ากับได้รับการประเมิน (ให้ 0) จากนั้น x คือ เพิ่มขึ้น (ให้ x = 1) และในที่สุดก็ดำเนินการมอบหมาย (ให้ x = 0 อีกครั้ง)
ทิมกู๊ดแมน

39

ฉันมาช้าไปงานปาร์ตี้นี้ แต่ฉันมี gotchas สองตัวที่กัดฉันทั้งคู่เมื่อเร็ว ๆ นี้:

การแก้ไขวันที่และเวลา

คุณสมบัติเห็บนั้นใช้เวลา 10 ล้านวินาทีต่อวินาที (100 นาโนวินาทีบล็อก) แต่ความละเอียดไม่ใช่ 100 นาโนวินาทีมันประมาณ 15 มิลลิวินาที

รหัสนี้:

long now = DateTime.Now.Ticks;
for (int i = 0; i < 10; i++)
{
    System.Threading.Thread.Sleep(1);
    Console.WriteLine(DateTime.Now.Ticks - now);
}

จะให้ผลลัพธ์ของ (ตัวอย่าง):

0
0
0
0
0
0
0
156254
156254
156254

ในทำนองเดียวกันถ้าคุณดูที่ DateTime.Now.Millisecond คุณจะได้รับค่าเป็นชิ้นส่วนโค้งมนที่ 15.625ms: 15, 31, 46 เป็นต้น

ลักษณะการทำงานนี้แตกต่างจากระบบสู่ระบบแต่มี gotchas ที่เกี่ยวข้องกับการแก้ปัญหาอื่น ๆใน API วันที่ / เวลานี้


Path.Combine

วิธีที่ยอดเยี่ยมในการรวมพา ธ ไฟล์ แต่ก็ไม่ได้ทำงานตามที่คุณคาดหวังเสมอไป

หากพารามิเตอร์ที่สองเริ่มต้นด้วย\ตัวอักษรมันจะไม่ให้เส้นทางที่สมบูรณ์:

รหัสนี้:

string prefix1 = "C:\\MyFolder\\MySubFolder";
string prefix2 = "C:\\MyFolder\\MySubFolder\\";
string suffix1 = "log\\";
string suffix2 = "\\log\\";

Console.WriteLine(Path.Combine(prefix1, suffix1));
Console.WriteLine(Path.Combine(prefix1, suffix2));
Console.WriteLine(Path.Combine(prefix2, suffix1));
Console.WriteLine(Path.Combine(prefix2, suffix2));

ให้ผลลัพธ์นี้กับคุณ:

C:\MyFolder\MySubFolder\log\
\log\
C:\MyFolder\MySubFolder\log\
\log\

17
การหาจำนวนครั้งในช่วงเวลา ~ 15ms ไม่ได้เกิดจากการขาดความแม่นยำในกลไกการจับเวลาพื้นฐาน (ฉันละเลยที่จะอธิบายอย่างละเอียดก่อนหน้านี้) นั่นเป็นเพราะแอพของคุณทำงานอยู่ในระบบปฏิบัติการหลายภารกิจ Windows จะตรวจสอบกับแอพของคุณทุก ๆ 15 มิลลิวินาทีและในช่วงเวลาเล็ก ๆ ที่ได้รับแอปของคุณจะประมวลผลข้อความทั้งหมดที่อยู่ในคิวตั้งแต่ชิ้นสุดท้ายของคุณ การโทรทั้งหมดของคุณภายในชิ้นนั้นจะส่งคืนในเวลาเดียวกันเพราะพวกเขาทั้งหมดได้อย่างมีประสิทธิภาพในเวลาเดียวกัน
MusiGenesis

2
@MusiGenesis: ฉันรู้ (ตอนนี้) มันทำงานอย่างไร แต่ดูเหมือนว่ามันทำให้ฉันเข้าใจผิดที่จะมีการวัดที่แม่นยำซึ่งไม่แม่นยำอย่างนั้นจริงๆ มันก็เหมือนกับการบอกว่าฉันรู้จักความสูงของฉันในหน่วยนาโนเมตรเมื่อฉันแค่ปัดเศษมันเป็นสิบล้านที่ใกล้ที่สุด
Damovisa

7
DateTime มีความสามารถในการจัดเก็บสูงถึงขีดเดียว; มันเป็น DateTime ตอนนี้ไม่ได้ใช้ความแม่นยำนั้น
Ruben

16
ส่วนเสริม '\' เป็น gotcha สำหรับผู้ใช้ unix / mac / linux จำนวนมาก ใน Windows ถ้ามี '\' นำหน้านั่นหมายความว่าเราต้องการไปที่รูทของไดรฟ์ (เช่น C :) ลองใช้CDคำสั่งเพื่อดูสิ่งที่ฉันหมายถึง .... 1) Goto C:\Windows\System322) Type CD \Users3) Woah! ตอนนี้คุณอยู่ที่C:\Users... GOT IT? ... Path.Combine (@ "C: \ Windows \ System32", @ "\ Users") ควรกลับมา\Usersซึ่งหมายความว่าแม่นยำ[current_drive_here]:\Users
chakrit

8
แม้จะไม่มี 'sleep' แต่ก็ทำเช่นเดียวกัน สิ่งนี้ไม่เกี่ยวข้องกับแอพที่มีกำหนดการทุก 15 มิลลิวินาที ฟังก์ชั่นพื้นเมืองที่เรียกว่าโดย DateTime.UtcNow, GetSystemTimeAsFileTime ดูเหมือนจะมีความละเอียดไม่ดี
Jimbo

38

เมื่อคุณเริ่มต้นกระบวนการ (ใช้ System.Diagnostics) ที่เขียนไปยังคอนโซล แต่คุณไม่เคยอ่านสตรีม Console.Out หลังจากเอาท์พุตจำนวนหนึ่งแอปของคุณจะปรากฏขึ้นเพื่อหยุดการทำงาน


3
สิ่งเดียวกันยังคงเกิดขึ้นได้เมื่อคุณเปลี่ยนเส้นทางทั้ง stdout และ stderr และใช้การเรียกใช้ ReadToEnd สองครั้งตามลำดับ เพื่อความปลอดภัยในการจัดการทั้ง stdout และ stderr คุณต้องสร้างเธรดการอ่านสำหรับแต่ละเธรด
30790 Sebastiaan M

34

ไม่มีทางลัดของโอเปอเรเตอร์ใน Linq-To-Sql

ดูที่นี่ที่นี่

ในระยะสั้นภายในส่วนเงื่อนไขของแบบสอบถาม Linq-To-Sql คุณไม่สามารถใช้ทางลัดตามเงื่อนไขเช่น||และ&&เพื่อหลีกเลี่ยงข้อยกเว้นการอ้างอิงที่เป็นโมฆะ Linq-To-Sql จะประเมินทั้งสองด้านของตัวดำเนินการ OR หรือ AND แม้ว่าเงื่อนไขแรกจะตัดความจำเป็นในการประเมินเงื่อนไขที่สอง!


8
TIL BRB ใหม่เพิ่มประสิทธิภาพไม่กี่ร้อยคำสั่ง LINQ ...
tsilb

30

การใช้พารามิเตอร์เริ่มต้นด้วยวิธีการเสมือน

abstract class Base
{
    public virtual void foo(string s = "base") { Console.WriteLine("base " + s); }
}

class Derived : Base
{
    public override void foo(string s = "derived") { Console.WriteLine("derived " + s); }
}

...

Base b = new Derived();
b.foo();

เอาต์พุต:
ฐานที่ได้รับ


10
แปลกฉันคิดว่านี่ชัดเจนอย่างสมบูรณ์ หากประเภทที่ประกาศคือBaseคอมไพเลอร์ควรจะรับค่าเริ่มต้นจากBaseที่ไหนถ้าไม่ ฉันคิดว่ามันเป็นgotcha อีกเล็กน้อยที่ค่าเริ่มต้นจะแตกต่างกันถ้าประเภทที่ประกาศเป็นประเภทที่ได้รับแม้ว่าวิธีการที่เรียกว่า (คงที่) เป็นวิธีการพื้นฐาน
Timwi

1
เหตุใดการใช้งานวิธีการหนึ่งจะได้รับค่าเริ่มต้นของการใช้งานอื่น
staafl

1
@staafl อาร์กิวเมนต์เริ่มต้นได้รับการแก้ไขในเวลารวบรวมไม่ใช่ runtime
fredoverflow

1
ฉันจะบอกว่า gotcha นี้เป็นพารามิเตอร์เริ่มต้นโดยทั่วไป - คนมักจะไม่ตระหนักว่าพวกเขาได้รับการแก้ไขในเวลารวบรวมมากกว่าเวลาทำงาน
Luaan

4
@ FredOverflow คำถามของฉันเป็นแนวคิด แม้ว่าพฤติกรรมดังกล่าวจะเหมาะสมกับการใช้งาน แต่ก็ไม่ได้ใช้งานง่ายและอาจเป็นสาเหตุของข้อผิดพลาดได้ IMHO คอมไพเลอร์ C # ไม่ควรอนุญาตให้เปลี่ยนค่าพารามิเตอร์เริ่มต้นเมื่อแทนที่
staafl

27

วัตถุค่าในคอลเลกชันที่ไม่แน่นอน

struct Point { ... }
List<Point> mypoints = ...;

mypoints[i].x = 10;

ไม่มีผล

mypoints[i]ส่งคืนสำเนาของPointวัตถุที่มีค่า C # อย่างมีความสุขช่วยให้คุณปรับเปลี่ยนเขตข้อมูลของการคัดลอก ไม่ทำอะไรเลยในใจ


อัปเดต: สิ่งนี้ดูเหมือนจะได้รับการแก้ไขใน C # 3.0:

Cannot modify the return value of 'System.Collections.Generic.List<Foo>.this[int]' because it is not a variable

6
ฉันสามารถดูสาเหตุที่ทำให้เกิดความสับสนโดยพิจารณาว่ามันใช้งานได้จริงกับอาร์เรย์ (ตรงกันข้ามกับคำตอบของคุณ) แต่ไม่ใช่กับคอลเลกชันแบบไดนามิกอื่น ๆ เช่น List <Point>
Lasse V. Karlsen

2
คุณถูก. ขอบคุณ ฉันแก้ไขคำตอบของฉัน :) arr[i].attr=เป็นไวยากรณ์พิเศษสำหรับอาร์เรย์ที่คุณไม่สามารถเขียนโค้ดในคอนเทนเนอร์ของไลบรารี (ทำไม (<value expression>) attr = <expr> อนุญาตให้ใช้ได้เลยทำไมมันถึงเข้าท่า?
Bjarke Ebert

1
@Barke Ebert: มีบางกรณีที่มันสมเหตุสมผล แต่น่าเสียดายที่ไม่มีวิธีใดที่คอมไพเลอร์จะระบุและอนุญาต ตัวอย่างสถานการณ์การใช้งาน: โครงสร้างที่ไม่เปลี่ยนรูปซึ่งเก็บการอ้างอิงไปยังอาร์เรย์สองมิติแบบสี่เหลี่ยมพร้อมกับตัวบ่งชี้ "หมุน / พลิก" struct จะไม่เปลี่ยนแปลงดังนั้นการเขียนองค์ประกอบของอินสแตนซ์แบบอ่านอย่างเดียวควรจะใช้ได้ แต่คอมไพเลอร์จะไม่ทราบว่าตัวตั้งค่าคุณสมบัติไม่ได้จริง ๆ แล้วจะเขียนโครงสร้างดังนั้นจึงไม่อนุญาต .
supercat

25

อาจไม่ใช่สิ่งที่แย่ที่สุด แต่บางส่วนของกรอบงาน. net ใช้องศาขณะที่คนอื่นใช้เรเดียน (และเอกสารที่ปรากฏใน Intellisense ไม่เคยบอกคุณว่าคุณต้องไปที่ MSDN เพื่อค้นหา)

ทั้งหมดนี้สามารถหลีกเลี่ยงได้โดยมีAngleชั้นเรียนแทน ...


ฉันประหลาดใจที่มีจำนวนผู้โหวตเพิ่มขึ้นเมื่อพิจารณา gotchas อื่น ๆ ของฉันแย่กว่านี้อย่างมาก
BlueRaja - Danny Pflughoeft

22

สำหรับโปรแกรมเมอร์ C / C ++ การเปลี่ยนไปใช้ C # นั้นเป็นเรื่องธรรมดา อย่างไรก็ตาม gotcha ที่ใหญ่ที่สุดที่ฉันเคยพบเป็นการส่วนตัว (และได้เห็นกับผู้อื่นที่ทำให้การเปลี่ยนแปลงเดียวกัน) ไม่เข้าใจความแตกต่างระหว่างคลาสและ structs ใน C # อย่างเต็มที่

ใน C ++ คลาสและ structs เหมือนกัน พวกเขาแตกต่างกันในการมองเห็นเริ่มต้นเท่านั้นที่คลาสเริ่มต้นการมองเห็นส่วนตัวและ structs เริ่มต้นการมองเห็นสาธารณะ ใน C ++ นิยามคลาสนี้

    class A
    {
    public:
        int i;
    };

เทียบเท่ากับนิยามของ struct นี้

    struct A
    {
        int i;
    };

อย่างไรก็ตามใน C # คลาสเป็นประเภทการอ้างอิงในขณะที่ structs เป็นประเภทค่า นี้จะทำให้บิ๊กความแตกต่างใน (1) การตัดสินใจเมื่อจะใช้หนึ่งในช่วงอื่น ๆ (2) การทดสอบความเสมอภาควัตถุ (3) ผลการดำเนินงาน (เช่นมวย / unboxing) ฯลฯ

มีข้อมูลทุกชนิดในเว็บที่เกี่ยวข้องกับความแตกต่างระหว่างสองสิ่ง (เช่นที่นี่ ) ฉันขอแนะนำให้ทุกคนที่ทำการเปลี่ยนมาใช้ภาษา C # เป็นอย่างน้อยมีความรู้ในการทำงานเกี่ยวกับความแตกต่างและความหมายของพวกเขา


13
ดังนั้น gotcha ที่เลวร้ายที่สุดคือคนที่ไม่ใส่ใจที่จะใช้เวลาในการเรียนรู้ภาษาก่อนที่จะใช้หรือไม่
BlueRaja - Danny Pflughoeft

3
@ BlueRaja-DannyPflughoeft เพิ่มเติมเช่น gotcha คลาสสิกของภาษาที่คล้ายกันที่เห็นได้ชัด - พวกเขาใช้คำหลักที่คล้ายกันมากและในหลายกรณีไวยากรณ์ แต่ทำงานในวิธีที่แตกต่างกันมาก
Luaan

19

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


2
บล็อกการใช้ () ใช้วิธีแก้ปัญหาอย่างประณีต เมื่อใดก็ตามที่คุณเห็นการเรียกให้ทิ้งคุณสามารถ refactor ทันทีเพื่อความปลอดภัยในการใช้ ()
Jeremy Frey

5
ฉันคิดว่าสิ่งที่น่ากังวลคือการนำ IDisposable ไปใช้อย่างถูกต้อง
Mark Brackett

4
ในทางกลับกันนิสัยการใช้ () สามารถกัดคุณโดยไม่คาดคิดเช่นเมื่อทำงานกับ PInvoke คุณไม่ต้องการกำจัดสิ่งที่ API ยังคงอ้างอิงอยู่
MusiGenesis

3
การใช้ IDisposable อย่างถูกต้องนั้นยากและเข้าใจได้แม้กระทั่งคำแนะนำที่ดีที่สุดที่ฉันพบในเรื่องนี้ (. NET Framework Guidelines) อาจทำให้เกิดความสับสนในการสมัครจนกว่าคุณจะ "รับ"
Quibblesome

1
คำแนะนำที่ดีที่สุดที่ฉันเคยพบใน IDisposable มาจาก Stephen Cleary รวมถึงกฎง่าย ๆ สามข้อและบทความเชิงลึกเกี่ยวกับ IDisposable
Roman Starkov

19

ใช้อาร์เรย์ IList

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

คอมไพล์ แต่ใช้งานไม่ได้:

IList<int> myList = new int[] { 1, 2, 4 };
myList.Add(5);

เรามีปัญหานี้มากเพราะ serializer (WCF) เปลี่ยน ILists ทั้งหมดเป็นอาร์เรย์และเราได้รับข้อผิดพลาดรันไทม์


8
IMHO ปัญหาคือ Microsoft ไม่มีอินเทอร์เฟซที่กำหนดไว้เพียงพอสำหรับคอลเลกชัน IMHO ควรมี iEnumerable, iMultipassEnumerable (รองรับการตั้งค่าใหม่และรับประกันว่าจะผ่านหลายรอบ), iLiveEnumerable (จะมีความหมายที่กำหนดไว้เพียงบางส่วนหากการเปลี่ยนแปลงการรวบรวมในระหว่างการแจงนับ - การเปลี่ยนแปลงอาจหรืออาจไม่ปรากฏในการแจงนับ ผลลัพธ์ปลอมหรือข้อยกเว้น), iReadIndexable, iReadWriteIndexable ฯลฯ เนื่องจากอินเตอร์เฟสสามารถ "สืบทอด" ส่วนต่อประสานอื่น ๆ ได้สิ่งนี้จะไม่เพิ่มงานพิเศษมากนักถ้ามี (มันจะบันทึก NotImplemented stubs)
supercat

@supercat ที่จะสร้างความสับสนว่าเป็นนรกสำหรับผู้เริ่มต้นและผู้เขียนโค้ดมานาน ฉันคิดว่าคอลเลกชัน. NET และอินเทอร์เฟซของพวกมันนั้นเยี่ยมยอด แต่ฉันขอขอบคุณความนอบน้อมของคุณ ;)
Jordan

@ จอร์แดน: ตั้งแต่เขียนข้างต้นฉันได้ตัดสินใจว่าวิธีที่ดีกว่าจะได้มีทั้งIEnumerable<T>และIEnumerator<T>สนับสนุนFeaturesคุณสมบัติเช่นเดียวกับวิธีการ "ตัวเลือก" บางอย่างที่มีประโยชน์จะถูกกำหนดโดยสิ่งที่ "คุณสมบัติ" รายงาน ฉันยืนตามจุดหลักของฉันแม้ว่าซึ่งมีกรณีที่รหัสที่ได้รับIEnumerable<T>จะต้องสัญญาที่แข็งแกร่งกว่าIEnumerable<T>ให้ การโทรToListจะให้สิ่งIEnumerable<T>ที่เป็นไปตามสัญญาดังกล่าว แต่ในหลายกรณีจะมีราคาแพงโดยไม่จำเป็น ฉันจะวางตัวว่าควรจะมี ...
supercat

... หมายถึงรหัสที่ได้รับIEnumerable<T>สามารถคัดลอกเนื้อหาถ้าจำเป็นแต่สามารถละเว้นจากการทำเช่นนั้นโดยไม่จำเป็น
supercat

ตัวเลือกของคุณไม่สามารถอ่านได้อย่างแน่นอน เมื่อฉันเห็น IList ในรหัสฉันรู้ว่าฉันกำลังทำงานกับอะไรแทนที่จะต้องตรวจสอบคุณสมบัติคุณสมบัติ โปรแกรมเมอร์ชอบที่จะลืมว่าคุณลักษณะที่สำคัญของรหัสคือมันสามารถอ่านได้โดยคนไม่ใช่แค่คอมพิวเตอร์ เนมสเปซคอลเลกชัน. NET นั้นไม่เหมาะ แต่ก็ดีและบางครั้งการหาทางออกที่ดีที่สุดนั้นไม่ใช่เรื่องของหลักการที่เหมาะสม รหัสที่แย่ที่สุดที่ฉันได้ทำงานด้วยคือรหัสที่พยายามให้พอดีกับ DRY ฉันทิ้งแล้วเขียนใหม่ มันเป็นรหัสที่ไม่ดี ฉันไม่ต้องการใช้กรอบงานของคุณเลย
Jordan

18

foreach ลูปขอบเขตตัวแปร!

var l = new List<Func<string>>();
var strings = new[] { "Lorem" , "ipsum", "dolor", "sit", "amet" };
foreach (var s in strings)
{
    l.Add(() => s);
}

foreach (var a in l)
    Console.WriteLine(a());

พิมพ์ห้า "amet" ในขณะที่ตัวอย่างต่อไปนี้ทำงานได้ดี

var l = new List<Func<string>>();
var strings = new[] { "Lorem" , "ipsum", "dolor", "sit", "amet" };
foreach (var s in strings)
{
    var t = s;
    l.Add(() => t);
}

foreach (var a in l)
    Console.WriteLine(a());

11
นี่เทียบเท่ากับตัวอย่างของ Jon ด้วยวิธีการที่ไม่ระบุชื่อ
Mehrdad Afshari

3
บันทึกว่ายิ่งสับสนกับหน้า foreach ที่ตัวแปร "s" ง่ายต่อการผสมกับตัวแปรที่มีขอบเขต ด้วยตัวแปร for-loops ทั่วไปตัวแปร index จะเหมือนกันสำหรับแต่ละการวนซ้ำ
Mikko Rantanen

2
blogs.msdn.com/ericlippert/archive/2009/11/12/…และใช่หวังว่าตัวแปรจะถูกกำหนดขอบเขต "เหมาะสม"
Roman Starkov

2
นี้ได้รับการแก้ไขใน C # 5
Johnbot

คุณแค่พิมพ์ตัวแปรเดียวกันซ้ำแล้วซ้ำอีกโดยไม่ต้องเปลี่ยน
Jordan

18

MS SQL Server ไม่สามารถจัดการกับวันที่ก่อนปี 1753 อย่างมีนัยสำคัญที่ไม่ได้ซิงค์กับDateTime.MinDateค่าคงที่. NET ซึ่งคือ 1/1/1 ดังนั้นถ้าคุณพยายามที่จะบันทึกใจวันที่ที่ไม่ถูกต้อง (ตามที่เพิ่งเกิดขึ้นกับฉันในการนำเข้าข้อมูล) หรือเพียงแค่วันเกิดของ William the Conqueror คุณจะเดือดร้อน ไม่มีวิธีแก้ไขเฉพาะหน้าสำหรับสิ่งนี้ หากคุณต้องการทำงานกับวันที่ก่อนปี 1753 คุณต้องเขียนวิธีแก้ปัญหาของคุณเอง


17
ค่อนข้างตรงไปตรงมาฉันคิดว่า MS SQL Server มีสิทธิ์นี้และ. Net ผิด หากคุณทำวิจัยแล้วคุณรู้ว่าวันที่ก่อนปี 1751 ได้รับขี้ขลาดเนื่องจากการเปลี่ยนแปลงปฏิทินวันข้ามอย่างสมบูรณ์ ฯลฯ RDBMs ส่วนใหญ่มีจุดตัดบางส่วน สิ่งนี้ควรให้จุดเริ่มต้นแก่คุณ: ancestry.com/learn/library/article.aspx?article=3358
NotMe

11
นอกจากนี้วันที่คือ 1,653 .. ซึ่งเป็นครั้งแรกที่เรามีปฏิทินอย่างต่อเนื่องโดยไม่ข้ามวันที่ SQL 2008 เปิดตัว Date และ datetime2 datetype ซึ่งสามารถยอมรับวันที่จาก 1/1/01 ถึง 12/31/9999 อย่างไรก็ตามการเปรียบเทียบวันที่โดยใช้ประเภทเหล่านั้นควรดูด้วยความสงสัยหากคุณเปรียบเทียบวันที่ก่อนปี 1753
NotMe

โอ้ถูกต้อง 1753 แก้ไขแล้วขอบคุณ
Shaul Behr

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

5
ผ่านวิกิพีเดียในวันจูเลียนคุณสามารถค้นหาโปรแกรมพื้นฐาน 13 บรรทัด CALJD.BAS ที่ตีพิมพ์ในปี 1984 ที่สามารถทำการคำนวณวันที่ย้อนหลังได้ถึงประมาณ 5,000 ปีก่อนคริสต์ศักราชโดยไม่คำนึงถึงวันก้าวกระโดดและวันที่ข้ามไปในปี 1753 ระบบอย่าง SQL2008 น่าจะแย่กว่านี้ คุณอาจไม่สนใจในการแสดงวันที่ที่ถูกต้องในศตวรรษที่ 15 แต่คนอื่น ๆ อาจและซอฟต์แวร์ของเราควรจัดการสิ่งนี้โดยไม่มีข้อบกพร่อง ปัญหาอื่นคือวินาทีกระโดด . .
Roland

18

Linq Caching Gotcha ที่น่ารังเกียจ

ดูคำถามของฉันที่นำไปสู่การค้นพบนี้และบล็อกเกอร์ที่ค้นพบปัญหา

กล่าวโดยย่อ DataContext เก็บแคชของวัตถุ Linq-to-Sql ทั้งหมดที่คุณเคยโหลด หากบุคคลอื่นทำการเปลี่ยนแปลงใด ๆ กับระเบียนที่คุณโหลดไว้ก่อนหน้านี้คุณจะไม่สามารถรับข้อมูลล่าสุดได้แม้ว่าคุณจะโหลดบันทึกซ้ำอย่างชัดเจน!

นี่เป็นเพราะคุณสมบัติเรียกว่าObjectTrackingEnabledDataContext ซึ่งโดยค่าเริ่มต้นเป็นจริง หากคุณตั้งค่าคุณสมบัตินั้นเป็นเท็จจะมีการโหลดระเบียนใหม่ทุกครั้ง ... แต่ ... คุณไม่สามารถยืนยันการเปลี่ยนแปลงใด ๆ ในระเบียนนั้นด้วย SubmitChanges ()

Gotcha!


Iv ใช้เวลาเพียงวันครึ่งไล่ลง BUG นี้ ... (และโหลดของผม!)
ผ่าตัด Coder

สิ่งนี้เรียกว่าความขัดแย้งที่เกิดขึ้นพร้อมกันและยังคงเป็น gotcha วันนี้แม้ว่าจะมีวิธีการบางอย่างในขณะนี้แม้ว่าพวกเขาจะมีแนวโน้มที่จะส่งหนัก DataContext เป็นฝันร้าย O_o
Jordan

17

สัญญาใน Stream.Readเป็นสิ่งที่ฉันได้เห็นการเดินทางของผู้คนจำนวนมาก:

// Read 8 bytes and turn them into a ulong
byte[] data = new byte[8];
stream.Read(data, 0, 8); // <-- WRONG!
ulong data = BitConverter.ToUInt64(data);

เหตุผลนี้ผิดที่Stream.Readจะอ่านจำนวนไบต์ที่ระบุมากที่สุดแต่มีอิสระที่จะอ่านเพียง 1 ไบต์แม้ว่าจะมีอีก 7 ไบต์ก่อนสิ้นกระแสก็ตาม

มันไม่ได้ช่วยที่มีลักษณะนี้เพื่อให้คล้ายกับStream.Writeที่มีการรับประกันว่าจะได้เขียนทุก bytes ถ้ามันกลับมาพร้อมกับไม่มีข้อยกเว้น นอกจากนี้ยังไม่ได้ช่วยให้รหัสข้างต้นทำงานเกือบตลอดเวลาทำงานเกือบตลอดเวลาและแน่นอนว่ามันไม่ได้ช่วยให้ไม่มีวิธีที่สะดวกและสะดวกในการอ่านอย่างถูกต้อง N ไบต์

ดังนั้นในการเสียบรูและเพิ่มการรับรู้นี่คือตัวอย่างของวิธีที่ถูกต้องในการทำสิ่งนี้:

    /// <summary>
    /// Attempts to fill the buffer with the specified number of bytes from the
    /// stream. If there are fewer bytes left in the stream than requested then
    /// all available bytes will be read into the buffer.
    /// </summary>
    /// <param name="stream">Stream to read from.</param>
    /// <param name="buffer">Buffer to write the bytes to.</param>
    /// <param name="offset">Offset at which to write the first byte read from
    ///                      the stream.</param>
    /// <param name="length">Number of bytes to read from the stream.</param>
    /// <returns>Number of bytes read from the stream into buffer. This may be
    ///          less than requested, but only if the stream ended before the
    ///          required number of bytes were read.</returns>
    public static int FillBuffer(this Stream stream,
                                 byte[] buffer, int offset, int length)
    {
        int totalRead = 0;
        while (length > 0)
        {
            var read = stream.Read(buffer, offset, length);
            if (read == 0)
                return totalRead;
            offset += read;
            length -= read;
            totalRead += read;
        }
        return totalRead;
    }

    /// <summary>
    /// Attempts to read the specified number of bytes from the stream. If
    /// there are fewer bytes left before the end of the stream, a shorter
    /// (possibly empty) array is returned.
    /// </summary>
    /// <param name="stream">Stream to read from.</param>
    /// <param name="length">Number of bytes to read from the stream.</param>
    public static byte[] Read(this Stream stream, int length)
    {
        byte[] buf = new byte[length];
        int read = stream.FillBuffer(buf, 0, length);
        if (read < length)
            Array.Resize(ref buf, read);
        return buf;
    }

1
var r = new BinaryReader(stream); ulong data = r.ReadUInt64();หรือในตัวอย่างที่ชัดเจนของคุณ: BinaryReader ก็มีFillBufferวิธีการเช่นกัน ...
jimbobmcgee

15

เหตุการณ์ที่เกิดขึ้น

ฉันไม่เคยเข้าใจว่าเหตุใดเหตุการณ์จึงเป็นคุณลักษณะภาษา มีความซับซ้อนในการใช้: คุณต้องตรวจสอบโมฆะก่อนโทรคุณต้องยกเลิกการลงทะเบียน (ตัวคุณเอง) คุณไม่สามารถหาผู้ที่ลงทะเบียนได้ (เช่น: ฉันลงทะเบียนแล้วหรือยัง) เหตุใดเหตุการณ์จึงไม่เป็นเพียงแค่คลาสในห้องสมุด โดยทั่วไปเป็นผู้เชี่ยวชาญList<delegate>หรือไม่?


1
การมัลติเธรดก็เจ็บปวดเช่นกัน ปัญหาทั้งหมดเหล่านี้ แต่สิ่งที่เป็นโมฆะได้รับการแก้ไขใน CAB (ซึ่งมีคุณสมบัติที่ควรสร้างเป็นภาษาจริง ๆ ) - มีการประกาศเหตุการณ์ทั่วโลกและวิธีการใดก็ตามสามารถประกาศตัวเองว่าเป็น "สมาชิก" ของเหตุการณ์ใด ๆ ปัญหาเดียวของฉันกับ CAB คือชื่อเหตุการณ์ที่ทั่วโลกนี้จะมีมากกว่า enums (ซึ่งอาจได้รับการแก้ไขโดย enums ฉลาดมากขึ้นเช่น Java มีซึ่งโดยเนื้อแท้ทำงานเป็นสตริง!) CAB เป็นเรื่องยากที่จะตั้งขึ้น แต่มีเป็นโคลนที่มาเปิดที่เรียบง่ายที่มีอยู่ที่นี่
BlueRaja - Danny Pflughoeft

3
ฉันไม่ชอบการนำเหตุการณ์. net ไปใช้ ควรจัดการกับการสมัครสมาชิกกิจกรรมโดยการเรียกวิธีการที่เพิ่มการสมัครสมาชิกและส่งกลับ IDisposable ซึ่งเมื่อ Dispose'd จะลบการสมัครสมาชิก ไม่จำเป็นต้องมีสิ่งก่อสร้างพิเศษที่รวมวิธีการ "เพิ่ม" และ "ลบ" ซึ่งความหมายสามารถหลบได้โดยเฉพาะอย่างยิ่งหากมีความพยายามที่จะเพิ่มและลบผู้แทนหลายผู้รับในภายหลัง (เช่นเพิ่ม "B" ตามด้วย "AB" จากนั้นลบ "B" (ออกจาก "BA") และ "AB" (ยังคงออกจาก "BA") อุ๊ปส์
supercat

@supercat คุณจะเขียนใหม่button.Click += (s, e) => { Console.WriteLine(s); }อย่างไร?
Ark-kun

ถ้าผมจะต้องสามารถที่จะยกเลิกการเป็นสมาชิกแยกต่างหากจากเหตุการณ์อื่น ๆและยกเลิกการเป็นสมาชิกผ่านทางIEventSubscription clickSubscription = button.SubscribeClick((s,e)=>{Console.WriteLine(s);}); clickSubscription.Dispose();หากวัตถุของฉันจะเก็บการสมัครสมาชิกทั้งหมดตลอดอายุการใช้งานของมันMySubscriptions.Add(button.SubscribeClick((s,e)=>{Console.WriteLine(s);}));แล้วMySubscriptions.Dispose()ฆ่าการสมัครทั้งหมด
supercat

@ Ark-kun: การเก็บวัตถุที่ห่อหุ้มการสมัครสมาชิกภายนอกอาจดูน่ารำคาญ แต่เกี่ยวกับการสมัครสมาชิกเนื่องจากเอนทิตีจะทำให้สามารถรวมพวกเขาด้วยประเภทที่สามารถมั่นใจได้ว่าพวกเขาทั้งหมดล้างสิ่งที่ยากมาก
supercat

14

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

class SomeGeneric<T>
{
    public static int i = 0;
}

class Test
{
    public static void main(string[] args)
    {
        SomeGeneric<int>.i = 5;
        SomeGeneric<string>.i = 10;
        Console.WriteLine(SomeGeneric<int>.i);
        Console.WriteLine(SomeGeneric<string>.i);
        Console.WriteLine(SomeGeneric<int>.i);
    }
}

สิ่งนี้พิมพ์ 5 10 5


5
คุณสามารถมีคลาสพื้นฐานที่ไม่ใช่แบบทั่วไปที่กำหนดสถิตยศาสตร์และสืบทอด generics จากมัน แม้ว่าฉันจะไม่เคยตกหลุมกับพฤติกรรมนี้ใน C # - ฉันยังจำชั่วโมงการดีบักที่ยาวนานของเทมเพลต C ++ บางตัวได้ ... Eww! :)
Paulius

7
แปลกฉันคิดว่านี่ชัดเจน เพียงแค่คิดเกี่ยวกับสิ่งที่มันควรจะทำอย่างไรถ้ามีประเภทi T
Timwi

1
Typeพารามิเตอร์ชนิดเป็นส่วนหนึ่งของ SomeGeneric<int>เป็นประเภทที่แตกต่างจากSomeGeneric<string>; แน่นอนว่าแต่ละคนมีของตัวเองpublic static int i
Radarbob

13

สามารถนับจำนวนได้มากกว่าหนึ่งครั้ง

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

ตัวอย่างเช่นในขณะที่เขียนการทดสอบบางอย่างฉันต้องการไฟล์ชั่วคราวบางอย่างเพื่อทดสอบตรรกะ:

var files = Enumerable.Range(0, 5)
    .Select(i => Path.GetTempFileName());

foreach (var file in files)
    File.WriteAllText(file, "HELLO WORLD!");

/* ... many lines of codes later ... */

foreach (var file in files)
    File.Delete(file);

ลองนึกภาพความประหลาดใจของฉันเมื่อFile.Delete(file)พ่นFileNotFound!!

สิ่งที่เกิดขึ้นที่นี่คือการfilesนับซ้ำได้ซ้ำสองครั้ง (ผลลัพธ์จากการทำซ้ำครั้งแรกจะไม่ถูกจดจำ) และในการทำซ้ำใหม่แต่ละครั้งที่คุณจะโทรซ้ำPath.GetTempFilename()ดังนั้นคุณจะได้รับชื่อไฟล์ชั่วคราว

แน่นอนว่าโซลูชันนี้มีความกระตือรือร้นที่จะระบุค่าโดยใช้ToArray()หรือToList():

var files = Enumerable.Range(0, 5)
    .Select(i => Path.GetTempFileName())
    .ToArray();

สิ่งนี้ยิ่งน่ากลัวเมื่อคุณทำอะไรแบบมัลติเธรดเช่น:

foreach (var file in files)
    content = content + File.ReadAllText(file);

และคุณรู้ว่าcontent.Lengthยังคงเป็น 0 หลังจากเขียนทั้งหมด !! จากนั้นคุณก็เริ่มตรวจสอบอย่างจริงจังว่าคุณไม่มีสภาพการแข่งขันเมื่อ ... หลังจากหนึ่งชั่วโมงที่สูญเปล่าไปแล้ว ... คุณก็รู้ว่ามันเป็นเพียงแค่สิ่งเล็ก ๆ น้อย ๆ ที่คุณลืมไปได้เลย!


นี่คือโดยการออกแบบ มันเรียกว่าการดำเนินการรอการตัดบัญชี เหนือสิ่งอื่นใดมันหมายถึงการจำลองการสร้าง TSQL ทุกครั้งที่คุณเลือกจากมุมมอง sql คุณจะได้ผลลัพธ์ที่แตกต่างกัน นอกจากนี้ยังอนุญาตการโยงซึ่งเป็นประโยชน์สำหรับที่เก็บข้อมูลระยะไกลเช่น SQL Server มิฉะนั้น x.Select.Where.Order โดยจะส่งคำสั่งแยก 3 รายการไปยังฐานข้อมูล ...
as9876

@AYS คุณคิดถึงคำว่า "Gotcha" ในชื่อคำถามหรือไม่
chakrit

ฉันคิดว่า gotcha หมายถึงการกำกับดูแลของนักออกแบบไม่ใช่สิ่งที่ตั้งใจ
as9876

อาจจะมีประเภทอื่นสำหรับ IEnumerable ที่ไม่สามารถรีสตาร์ทได้ ชอบ AutoBufferedEnumerable ไหม หนึ่งสามารถใช้งานได้อย่างง่ายดาย ดูเหมือนว่า gotcha ส่วนใหญ่เนื่องจากโปรแกรมเมอร์ขาดความรู้ฉันไม่คิดว่ามีอะไรผิดปกติกับพฤติกรรมปัจจุบัน
Eldritch Conundrum

13

เพิ่งพบหนึ่งแปลกที่ฉันติดอยู่ในการแก้ปัญหาในขณะที่:

คุณสามารถเพิ่มค่า null สำหรับ int ที่สามารถเป็นโมฆะได้โดยไม่ต้องทำการเว้นระยะห่าง

int? i = null;
i++; // I would have expected an exception but runs fine and stays as null

นั่นเป็นผลมาจากวิธีการที่ C # ใช้ประโยชน์จากการดำเนินงานสำหรับประเภท nullable มันคล้ายกับ NaN ที่กินทุกอย่างที่คุณโยนไป
IllidanS4 ต้องการโมนิก้ากลับเมื่อ

10
TextInfo textInfo = Thread.CurrentThread.CurrentCulture.TextInfo;

textInfo.ToTitleCase("hello world!"); //Returns "Hello World!"
textInfo.ToTitleCase("hElLo WoRld!"); //Returns "Hello World!"
textInfo.ToTitleCase("Hello World!"); //Returns "Hello World!"
textInfo.ToTitleCase("HELLO WORLD!"); //Returns "HELLO WORLD!"

ใช่พฤติกรรมนี้ได้รับการบันทึกไว้ แต่ไม่สามารถทำให้ถูกต้องได้


5
ฉันไม่เห็นด้วย - เมื่อคำหนึ่งอยู่ในตัวพิมพ์ใหญ่ทั้งหมดอาจมีความหมายพิเศษที่คุณไม่ต้องการยุ่งกับ Title Case เช่น "ประธานาธิบดีแห่งสหรัฐอเมริกา" -> "ประธานาธิบดีแห่งสหรัฐอเมริกา" ไม่ใช่ "ประธานาธิบดีแห่งสหรัฐอเมริกา" สหรัฐอเมริกา".
Shaul Behr

5
@Shaul: ในกรณีนี้พวกเขาควรระบุว่าเป็นพารามิเตอร์เพื่อหลีกเลี่ยงความสับสนเพราะฉันไม่เคยพบใครที่คาดว่าพฤติกรรมนี้ล่วงหน้า - ซึ่งทำให้gotcha !
BlueRaja - Danny Pflughoeft
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.