Trường hợp góc kỳ lạ nhất bạn từng thấy trong C # hoặc .NET là gì? [đóng cửa]


322

Tôi thu thập một vài trường hợp góc và trêu ghẹo não và luôn muốn nghe thêm. Trang chỉ thực sự bao gồm các bit ngôn ngữ C # và bobs, nhưng tôi cũng thấy những điều cốt lõi .NET cũng thú vị. Ví dụ: đây là một cái không có trên trang, nhưng tôi thấy không thể tin được:

string x = new string(new char[0]);
string y = new string(new char[0]);
Console.WriteLine(object.ReferenceEquals(x, y));

Tôi mong muốn in sai - sau tất cả, "mới" (với loại tham chiếu) luôn luôn tạo một đối tượng mới, phải không? Thông số kỹ thuật cho cả C # và CLI chỉ ra rằng nó nên. Vâng, không phải trong trường hợp cụ thể này. Nó in True và đã thực hiện trên mọi phiên bản của khung mà tôi đã thử nghiệm. (Tôi chưa thử nó trên Mono, phải thừa nhận ...)

Nói rõ hơn, đây chỉ là một ví dụ về loại điều tôi đang tìm kiếm - tôi không đặc biệt tìm kiếm cuộc thảo luận / giải thích về sự kỳ quặc này. (Nó không giống như thực tập chuỗi bình thường; đặc biệt, thực tập chuỗi không thường xảy ra khi một hàm tạo được gọi.) Tôi thực sự yêu cầu hành vi kỳ quặc tương tự.

Bất kỳ đá quý khác ẩn nấp ngoài đó?


64
Đã thử nghiệm trên Mono 2.0 RC; trả về True
Marc Gravell

10
cả hai chuỗi kết thúc là chuỗi .Empty và có vẻ như khung công tác chỉ giữ một tham chiếu đến đó
Adrian Zanescu

34
Đó là một điều bảo tồn bộ nhớ. Tra cứu tài liệu MSDN cho chuỗi phương thức tĩnh. Nội bộ. CLR duy trì một chuỗi chuỗi. Đó là lý do tại sao các chuỗi có nội dung giống hệt nhau hiển thị dưới dạng tham chiếu đến cùng một bộ nhớ tức là đối tượng.
John Leidegren

12
@ John: Thực tập chuỗi chỉ tự động xảy ra đối với chữ . Đó không phải là trường hợp ở đây. @DanielSwe: Thực tập không cần thiết để làm cho chuỗi bất biến. Thực tế là nó có thể là một hệ quả tốt đẹp của sự bất biến, nhưng thực tế bình thường không xảy ra ở đây.
Jon Skeet

3
Chi tiết triển khai gây ra hành vi này được giải thích tại đây: blog.liranchen.com/2010/08/brain-tease-with-strings.html
Liran

Câu trả lời:


394

Tôi nghĩ rằng tôi đã cho bạn xem cái này trước đây, nhưng tôi thích sự thú vị ở đây - điều này cần một số gỡ lỗi để theo dõi! (mã ban đầu rõ ràng phức tạp và tinh tế hơn ...)

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

Vậy T ...

Trả lời: bất kỳ Nullable<T>- chẳng hạn như int?. Tất cả các phương thức đều bị ghi đè, ngoại trừ GetType () không thể; do đó, nó được truyền (đóng hộp) cho đối tượng (và do đó là null) để gọi object.GetType () ... sẽ gọi null ;-p


Cập nhật: cốt truyện dày lên ... Ayende Rahien đã đưa ra một thử thách tương tự trên blog của mình , nhưng với 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?");
}

Nhưng nó có thể bị đánh bại! Sử dụng cùng một hướng dẫn được sử dụng bởi những thứ như điều khiển từ xa; cảnh báo - sau đây là tà ác :

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

Với vị trí này, new()cuộc gọi được chuyển hướng đến proxy ( MyFunnyProxyAttribute), trả về null. Bây giờ đi và rửa mắt!


9
Tại sao không thể xác định Nullable <T> .GetType ()? Không phải kết quả sẽ là typeof (Nullable <T>)?
vẽ Noakes

69
Drew: vấn đề là GetType () không phải là ảo, vì vậy nó không bị ghi đè - điều đó có nghĩa là giá trị được đóng hộp cho lệnh gọi phương thức. Hộp trở thành một tham chiếu null, do đó NRE.
Jon Skeet

10
@Đã vẽ; ngoài ra, có các quy tắc quyền anh đặc biệt cho Nullable <T>, có nghĩa là một hộp Nullable <T> trống thành null, không phải là hộp chứa Nullable <T> trống (và một hộp không null cho Nullable <T trống rỗng >)
Marc Gravell

29
Rất, rất tuyệt. Trong một cách không hay. ;-)
Konrad Rudolph

6
Trình xây dựng-ràng buộc, 10.1.5 trong thông số ngôn ngữ C # 3.0
Marc Gravell

216

Làm tròn ngân hàng.

Đây không phải là một lỗi trình biên dịch hay trục trặc, nhưng chắc chắn là một trường hợp góc lạ ...

.Net Framework sử dụng sơ đồ hoặc làm tròn được gọi là Làm tròn ngân hàng.

Trong Làm tròn Ngân hàng, các số 0,5 được làm tròn đến số chẵn gần nhất, vì vậy

Math.Round(-0.5) == 0
Math.Round(0.5) == 0
Math.Round(1.5) == 2
Math.Round(2.5) == 2
etc...

Điều này có thể dẫn đến một số lỗi bất ngờ trong tính toán tài chính dựa trên cách làm tròn nửa nổi tiếng hơn.

Điều này cũng đúng với Visual Basic.


22
Nó có vẻ xa lạ với tôi quá. Đó là, ít nhất, cho đến khi tôi có một danh sách lớn các số và tính tổng của chúng. Sau đó, bạn nhận ra rằng nếu bạn chỉ cần làm tròn số, bạn sẽ kết thúc với sự khác biệt rất lớn so với tổng của các số không làm tròn. Rất tệ nếu bạn đang làm các tính toán tài chính!
Tsvetomir Tsonev

255
Trong trường hợp mọi người không biết, bạn có thể làm: Math.Round (x, MidpointRounding.AwayFromZero); Để thay đổi sơ đồ làm tròn.
ICR

26
Từ các tài liệu: Hành vi của phương pháp này tuân theo Tiêu chuẩn IEEE 754, phần 4. Loại làm tròn này đôi khi được gọi là làm tròn đến gần nhất hoặc làm tròn số của ngân hàng. Nó giảm thiểu các lỗi làm tròn xuất phát từ việc làm tròn một giá trị trung điểm theo một hướng duy nhất.
ICR

8
Tôi tự hỏi nếu đây là lý do tại sao tôi thấy int(fVal + 0.5)rất thường xuyên ngay cả trong các ngôn ngữ có chức năng làm tròn tích hợp.
Ben Trống

32
Trớ trêu thay, tôi đã làm việc tại một ngân hàng một lần và các lập trình viên khác bắt đầu lật tẩy về điều này, nghĩ rằng việc làm tròn đã bị phá vỡ trong khuôn khổ
dan

176

Hàm này sẽ làm gì nếu được gọi là Rec(0)(không thuộc trình gỡ lỗi)?

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

Câu trả lời:

  • Trên JIT 32 bit, nó sẽ dẫn đến StackOverflowException
  • Trên JIT 64 bit, nó sẽ in tất cả các số sang int.MaxValue

Điều này là do trình biên dịch JIT 64 bit áp dụng tối ưu hóa cuộc gọi đuôi , trong khi JIT 32 bit thì không.

Thật không may, tôi không có máy 64 bit để xác minh điều này, nhưng phương pháp này đáp ứng tất cả các điều kiện để tối ưu hóa cuộc gọi đuôi. Nếu có ai đó tôi sẽ quan tâm xem điều đó có đúng không.


10
Phải được biên dịch trong chế độ phát hành, nhưng chắc chắn hoạt động tốt nhất trên x64 =)
Neil Williams

3
có thể đáng để cập nhật câu trả lời của bạn khi VS 2010 xuất hiện vì tất cả các JIT hiện tại sau đó sẽ thực hiện TCO ở chế độ Phát hành
ShuggyCoUk

3
Chỉ cần thử trên VS2010 Beta 1 trên WinXP 32 bit. Vẫn nhận được một StackOverflowException.
squillman

130
+1 cho StackOverflowException
calvinlough

7
Điều ++đó đã hoàn toàn ném tôi đi. Bạn không thể gọi Rec(i + 1)như một người bình thường?
cấu hình

111

Chỉ định cái này!


Đây là một câu hỏi mà tôi muốn hỏi trong các bữa tiệc (có lẽ là lý do tại sao tôi không được mời nữa):

Bạn có thể thực hiện các đoạn mã sau đây không?

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

Một mánh gian lận dễ dàng có thể là:

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

Nhưng giải pháp thực sự là thế này:

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

Vì vậy, có một thực tế ít người biết rằng các loại giá trị (cấu trúc) có thể gán lại thisbiến của chúng .


3
Các lớp C ++ cũng có thể làm điều đó ... như tôi đã phát hiện ra gần đây, chỉ bị mắng vì thực sự cố gắng sử dụng nó để tối ưu hóa: p
mpen

1
Tôi đã sử dụng tại chỗ mới thực sự. Chỉ muốn một cách hiệu quả để cập nhật tất cả các lĩnh vực :)
mpen

70
Đây cũng là một //this = new Teaser();
mánh

17
:-) Tôi thích những mánh gian lận trong mã sản xuất của mình hơn là sự ghê tởm tái chỉ định này ...
Omer Mor

2
Từ CLR qua C #: Lý do họ thực hiện điều này là do bạn có thể gọi hàm tạo không tham số của một cấu trúc trong một hàm tạo khác. Nếu bạn chỉ muốn khởi tạo một giá trị của một cấu trúc và muốn các giá trị khác bằng 0 / null (mặc định), bạn có thể viết public Foo(int bar){this = new Foo(); specialVar = bar;}. Điều này không hiệu quả và không thực sự hợp lý ( specialVarđược chỉ định hai lần), mà chỉ là FYI. (Đó là lý do được đưa ra trong cuốn sách, tôi không biết tại sao chúng ta không nên làm public Foo(int bar) : this())
kizzx2

100

Vài năm trước, khi làm việc trong chương trình trung học, chúng tôi đã gặp vấn đề với số điểm được trao cho khách hàng. Vấn đề liên quan đến việc truyền / chuyển đổi gấp đôi thành int.

Trong mã dưới đây:

double d = 13.6;

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

i1 == i2 ?

Hóa ra i1! = I2. Do các chính sách làm tròn khác nhau trong Chuyển đổi và truyền toán tử, các giá trị thực tế là:

i1 == 14
i2 == 13

Luôn luôn tốt hơn để gọi Math.COVA () hoặc Math.Floor () (hoặc Math.Round với MidpointRounding đáp ứng các yêu cầu của chúng tôi)

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

44
Truyền tới một số nguyên không làm tròn, nó chỉ cắt nó ra (luôn luôn làm tròn xuống). Vì vậy, điều này làm cho ý nghĩa hoàn chỉnh.
Tối đa

57
@Max: có, nhưng tại sao Convert round?
Stefan Steinegger

18
@Stefan Steinegger Nếu tất cả những gì nó được thực hiện, sẽ không có lý do gì cho nó ngay từ đầu, phải không? Cũng lưu ý rằng tên lớp là Convert not Cast.
bug-a-lot

3
Trong VB: vòng CInt (). Sửa () cắt ngắn. Đốt cháy tôi một lần ( blog.wassupy.com/2006/01/i-can-believe-it-not-truncating.html )
Michael Haren

74

Họ nên tạo 0 một số nguyên ngay cả khi quá tải hàm enum.

Tôi biết lý do nhóm cốt lõi C # để ánh xạ 0 đến enum, nhưng vẫn không phải là trực giao như mong muốn. Ví dụ từ Npgsql .

Ví dụ kiểm tra:

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
Wow đó là một cái mới cho tôi. Cũng muốn biết cách ConverTo.ToIn32 () hoạt động nhưng chuyển sang (int) 0 thì không. Và bất kỳ số nào khác> 0 hoạt động. (Bằng "hoạt động", ý tôi là gọi quá tải đối tượng.)
Lucas

Có một quy tắc phân tích mã được đề xuất để thực thi các thực tiễn tốt xung quanh hành vi này: msdn.microsoft.com/en-us/l Library / ms182149% 28VS.80% 29.aspx Liên kết đó cũng chứa một mô tả hay về cách hoạt động của ánh xạ 0 .
Chris Clark

1
@Chris Clark: tôi đã thử đặt Không = 0 vào Biểu tượng enum. vẫn là trình biên dịch chọn enum cho 0 và chẵn (int) 0
Michael Buen

2
IMO họ nên giới thiệu một từ khóa nonecó thể được sử dụng được chuyển đổi thành bất kỳ enum nào và biến 0 luôn thành một int và không hoàn toàn có thể chuyển đổi thành enum.
CodeInChaos

5
ConverTo.ToIn32 () hoạt động vì kết quả của nó không phải là hằng số đồng bộ. Và chỉ có hằng số tổng hợp 0 ​​là có thể chuyển đổi thành enum. Trong các phiên bản trước của .net thậm chí chỉ có nghĩa đen0 mới có thể chuyển đổi thành enum. Xem Eric Lippert blog: blogs.msdn.com/b/ericlippert/archive/2006/03/28/563282.aspx
CodesInChaos

67

Đây là một trong những điều bất thường nhất tôi từng thấy cho đến nay (tất nhiên là ngoài những thứ ở đây!):

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

Nó cho phép bạn khai báo nhưng không có công dụng thực sự, vì nó sẽ luôn yêu cầu bạn bọc bất cứ lớp nào bạn nhét vào trung tâm với một con Rùa khác.

[đùa] Tôi đoán đó là rùa suốt ... [/ đùa]


34
Tuy nhiên, bạn có thể tạo các trường hợp:class RealTurtle : Turtle<RealTurtle> { } RealTurtle t = new RealTurtle();
Marc Gravell

24
Thật. Đây là mô hình mà các enum Java sử dụng để có hiệu quả tuyệt vời. Tôi cũng sử dụng nó trong Bộ đệm giao thức.
Jon Skeet

6
RCIX, oh đúng vậy.
Joshua

8
Tôi đã sử dụng mô hình này khá nhiều trong các công cụ chung chung ưa thích. Nó cho phép những thứ như một bản sao được gõ chính xác hoặc tạo các thể hiện của chính nó.
Lucero

20
Đây là 'mẫu mẫu lặp lại một cách tò mò' en.wikipedia.org/wiki/Cantlyly_recurring_template_potype
porges

65

Đây là một cái tôi chỉ phát hiện ra gần đây ...

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

Cái nhìn bên trên thoạt nhìn có vẻ điên rồ, nhưng thực sựhợp pháp. Không, thực sự (mặc dù tôi đã bỏ lỡ một phần quan trọng, nhưng nó không phải là bất cứ điều gì khó hiểu như "thêm một lớp được gọi là IFoo" hoặc "thêm một usingbí danh để chỉIFoo vào một lớp học").

Xem nếu bạn có thể tìm ra lý do tại sao, sau đó: Ai nói bạn không thể khởi tạo giao diện?


1
+1 cho "sử dụng bí danh" - Tôi không bao giờ biết bạn có thể làm điều đó !
David

hack trong trình biên dịch cho COM Interop :-)
Ion Todirel

Đồ khốn! Ít nhất bạn có thể nói "trong một số trường hợp nhất định" ... Trình biên dịch của tôi không chấp nhận!
MA Hanin

56

Khi nào thì Boolean không đúng cũng không sai?

Bill phát hiện ra rằng bạn có thể hack một boolean để nếu A là True và B là True, (A và B) là Sai.

Booleans bị hack


134
Khi đó là FILE_NOT_FOUND, tất nhiên!
Greg

12
Điều này rất thú vị bởi vì nó có nghĩa là, về mặt toán học, rằng không có tuyên bố nào trong C # là có thể chứng minh được. Ôi
Simon Johnson

20
Một ngày nào đó tôi sẽ viết một chương trình phụ thuộc vào hành vi này và những con quỷ của địa ngục đen tối nhất sẽ chuẩn bị chào đón tôi. Bwahahahahaha!
Jeffrey L Whitledge

18
Ví dụ này sử dụng bitwise, không phải toán tử logic. Làm thế nào là đáng ngạc nhiên?
Josh Lee

6
Chà, anh ta hack bố cục của struct, tất nhiên bạn sẽ nhận được kết quả kỳ lạ, điều này không có gì đáng ngạc nhiên hay bất ngờ!
Ion Todirel

47

Tôi đến bữa tiệc muộn một chút, nhưng tôi có ba bốn năm:

  1. Nếu bạn thăm dò InvokeRequired trên một điều khiển chưa được tải / hiển thị, nó sẽ nói sai - và làm nổ tung khuôn mặt của bạn nếu bạn cố gắng thay đổi nó từ một luồng khác ( giải pháp là tham chiếu điều này. Xử lý trong trình tạo của điều khiển).

  2. Một cái khác làm tôi vấp ngã là một hội nghị với:

    enum MyEnum
    {
        Red,
        Blue,
    }

    nếu bạn tính toán MyEnum.Red.ToString () trong một hội đồng khác và trong khoảng thời gian ai đó đã biên dịch lại enum của bạn để:

    enum MyEnum
    {
        Black,
        Red,
        Blue,
    }

    tại thời gian chạy, bạn sẽ nhận được "Đen".

  3. Tôi đã có một hội nghị chia sẻ với một số hằng số tiện dụng. Người tiền nhiệm của tôi đã để lại vô số tài sản chỉ trông xấu xí, tôi nghĩ rằng tôi đã thoát khỏi sự lộn xộn và chỉ sử dụng const const. Tôi đã hơn một chút ngạc nhiên khi VS biên dịch chúng theo giá trị của chúng, và không tham khảo.

  4. Nếu bạn triển khai một phương thức mới của giao diện từ một hội đồng khác, nhưng bạn xây dựng lại tham chiếu phiên bản cũ của hội đồng đó, bạn sẽ nhận được TypeLoadException (không triển khai 'NewMethod'), mặc dù bạn đã triển khai nó (xem tại đây ).

  5. Từ điển <,>: "Thứ tự trả lại các mục không xác định". Điều này thật kinh khủng , bởi vì đôi khi nó có thể cắn bạn, nhưng làm việc cho người khác, và nếu bạn chỉ mù quáng cho rằng Từ điển sẽ chơi tốt ("tại sao không nên? Tôi nghĩ, Danh sách có"), bạn thực sự phải có mũi của bạn trong đó trước khi bạn bắt đầu đặt câu hỏi về giả định của bạn.


6
# 2 là một ví dụ thú vị. Enums là ánh xạ trình biên dịch đến các giá trị tích phân. Vì vậy, mặc dù bạn không gán giá trị cho chúng một cách rõ ràng, trình biên dịch đã làm, dẫn đến MyEnum.Red = 0 và MyEnum.Blue = 1. Khi bạn thêm Đen, bạn đã xác định lại giá trị 0 để ánh xạ từ Đỏ sang Đen. Tôi nghi ngờ rằng vấn đề cũng đã được biểu hiện trong các cách sử dụng khác, chẳng hạn như Tuần tự hóa.
LBushkin

3
Yêu cầu +1 cho Yêu cầu. Tại chúng tôi, chúng tôi muốn gán các giá trị một cách rõ ràng cho các enum như Red = 1, Blue = 2 để có thể chèn một giá trị mới trước hoặc sau nó sẽ luôn dẫn đến cùng một giá trị. Nó đặc biệt cần thiết nếu bạn đang lưu các giá trị vào cơ sở dữ liệu.
TheVillageIdiot

53
Tôi không đồng ý rằng # 5 là một "trường hợp cạnh". Từ điển không nên có một thứ tự được xác định dựa trên khi bạn chèn giá trị. Nếu bạn muốn một thứ tự được xác định, hãy sử dụng Danh sách hoặc sử dụng khóa có thể được sắp xếp theo cách hữu ích cho bạn hoặc sử dụng cấu trúc dữ liệu hoàn toàn khác.
Nêm

21
@Wedge, như SortedDixi có lẽ?
Allon Guralnek

4
# 3 xảy ra vì các hằng số được chèn dưới dạng chữ ở mọi nơi chúng được sử dụng (ít nhất là trong C #). Người tiền nhiệm của bạn có thể đã nhận thấy nó, đó là lý do tại sao họ sử dụng tài sản chỉ nhận. Tuy nhiên, một biến chỉ đọc (trái ngược với const) cũng sẽ hoạt động tốt.
Remoun

33

VB.NET, nullables và toán tử ternary:

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

Điều này làm tôi mất một thời gian để gỡ lỗi, vì tôi dự kiến ​​sẽ ichứa Nothing.

Tôi thực sự chứa gì? 0.

Đây là hành vi đáng ngạc nhiên nhưng thực sự "chính xác": Nothingtrong VB.NET không hoàn toàn giống như nulltrong CLR: Nothingcó thể có nghĩa nullhoặc default(T)cho một loại giá trị T, tùy thuộc vào ngữ cảnh. Trong trường hợp trên, Ifinfers Integerlà loại phổ biến Nothing5, vì vậy, trong trường hợp này, Nothingcó nghĩa là 0.


Thật thú vị, tôi đã không tìm thấy câu trả lời này vì vậy tôi phải tạo ra một câu hỏi . Vâng, ai biết câu trả lời là trong chủ đề này?
GSerg

28

Tôi tìm thấy một trường hợp góc thứ hai thực sự kỳ lạ, đánh bại người đầu tiên của tôi bằng một cú sút xa.

Phương thức String.Equals (Chuỗi, Chuỗi, StringComparison) không thực sự có tác dụng phụ.

Tôi đã làm việc trên một khối mã có mã này trên một dòng ở đầu một số chức năng:

stringvariable1.Equals(stringvariable2, StringComparison.InvariantCultureIgnoreCase);

Loại bỏ dòng đó dẫn đến tràn ngăn xếp ở một nơi khác trong chương trình.

Mã hóa ra là đang cài đặt một trình xử lý cho những gì thực chất là một sự kiện BeforeAssuggingLoad và cố gắng thực hiện

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

Bây giờ tôi không cần phải nói với bạn. Sử dụng văn hóa chưa được sử dụng trước đây trong so sánh chuỗi gây ra tải lắp ráp. InvariantCARM không phải là một ngoại lệ cho điều này.


Tôi đoán "tải một hội đồng" là một tác dụng phụ, vì bạn có thể quan sát nó với BeforeAssuggingLoad!
Jacob Krall

2
Ồ Đây là một phát bắn hoàn hảo vào chân của người bảo trì. Tôi đoán việc viết một trình xử lý BeforeAssuggingLoad có thể dẫn đến nhiều điều bất ngờ như vậy.
tóc giả

20

Dưới đây là một ví dụ về cách bạn có thể tạo cấu trúc gây ra thông báo lỗi "Đã cố đọc hoặc ghi bộ nhớ được bảo vệ. Đây thường là dấu hiệu cho thấy bộ nhớ khác bị hỏng". Sự khác biệt giữa thành công và thất bại là rất tinh tế.

Các bài kiểm tra đơn vị sau đây cho thấy vấn đề.

Xem nếu bạn có thể tìm ra những gì đã sai.

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

Đầu tôi đau ... Tại sao việc này không hiệu quả?
jasonh

2
Tôi đã viết cái này vài tháng trước, nhưng tôi không thể nhớ chính xác tại sao nó lại xảy ra.
cbp

10
Trông giống như một lỗi biên dịch; các += 500cuộc gọi: ldc.i4 500(đẩy 500 dưới dạng Int32), sau đó call valuetype Program/MyStruct Program/MyStruct::op_Addition(valuetype Program/MyStruct, valuetype [mscorlib]System.Decimal)- do đó, nó xử lý như một decimal(96 bit) mà không có bất kỳ chuyển đổi nào. Nếu bạn sử dụng += 500Mnó sẽ đúng. Nó chỉ đơn giản trông giống như trình biên dịch nghĩ rằng nó có thể thực hiện theo một cách (có lẽ là do toán tử int ẩn) và sau đó quyết định thực hiện theo cách khác.
Marc Gravell

1
Xin lỗi cho bài viết đôi, đây là một lời giải thích có trình độ hơn. Tôi sẽ thêm cái này, tôi đã từng chút một và cái này thật tệ, mặc dù tôi hiểu tại sao nó lại xảy ra. Đối với tôi đây là một hạn chế đáng tiếc của struct / valuetype. byte.com/topic/net/answers/ trộm
Bennett Dill

2
@Ben nhận được lỗi trình biên dịch hoặc sửa đổi không ảnh hưởng đến cấu trúc ban đầu là tốt. Một vi phạm truy cập là một con thú khác. Thời gian chạy không bao giờ nên ném nó nếu bạn chỉ viết mã được quản lý thuần túy an toàn.
CodeInChaos

18

C # hỗ trợ chuyển đổi giữa các mảng và danh sách miễn là các mảng không đa chiều và có mối quan hệ thừa kế giữa các loại và các loại là các loại tham chiếu

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

Lưu ý rằng điều này không hoạt động:

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
Ví dụ <T> IList chỉ là một dàn diễn viên, bởi vì chuỗi [] đã triển khai IClonizable, IList, ICollection, IEnumerable, IList <string>, ICollection <string> và IEnumerable <string>.
Lucas

15

Đây là điều kỳ lạ nhất mà tôi gặp phải một cách tình cờ:

public class DummyObject
{
    public override string ToString()
    {
        return null;
    }
}

Được sử dụng như sau:

DummyObject obj = new DummyObject();
Console.WriteLine("The text: " + obj.GetType() + " is " + obj);

Sẽ ném a NullReferenceException. Hóa ra nhiều bổ sung được trình biên dịch C # biên dịch thành một cuộc gọi đến String.Concat(object[]). Trước .NET 4, có một lỗi trong tình trạng quá tải của Concat nơi đối tượng được kiểm tra null, nhưng không phải là kết quả của 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;

Đây là một lỗi của ECMA-334 §14.7.4:

Toán tử nhị phân + thực hiện nối chuỗi khi một hoặc cả hai toán hạng có kiểu string. Nếu một toán hạng của nối chuỗi là null, một chuỗi rỗng được thay thế. Mặt khác, bất kỳ toán hạng không chuỗi nào được chuyển đổi thành biểu diễn chuỗi của nó bằng cách gọi ToStringphương thức ảo được kế thừa từ kiểu object. Nếu ToStringtrả về null, một chuỗi rỗng được thay thế.


3
Hmm, nhưng tôi có thể tưởng tượng lỗi này là .ToStringthực sự không bao giờ nên trả về null, nhưng chuỗi.Empty. Tuy nhiên và lỗi trong khuôn khổ.
Dykam

12

Thú vị - khi lần đầu tiên tôi nhìn vào đó, tôi đã cho rằng đó là thứ mà trình biên dịch C # đang kiểm tra, nhưng ngay cả khi bạn phát ra IL trực tiếp để loại bỏ bất kỳ cơ hội can thiệp nào, điều đó có nghĩa là nó thực sự là newobjmã op đang thực hiện kiểm tra.

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

Nó cũng tương đương với việc truebạn kiểm tra xem string.Emptyđiều đó có nghĩa là mã op này phải có hành vi đặc biệt đối với các chuỗi rỗng.


không phải là một aleck thông minh hoặc bất cứ điều gì nhưng bạn đã nghe nói về phản xạ ? nó khá tiện dụng trong những trường hợp như thế này;
RCIX

3
Bạn không thông minh; bạn đang thiếu điểm - Tôi muốn tạo IL cụ thể cho trường hợp này. Và dù sao đi nữa, với Reflection.Emit là tầm thường đối với loại kịch bản này, có lẽ nhanh như viết một chương trình trong C # sau đó mở phản xạ, tìm nhị phân, tìm phương thức, v.v ... Và tôi thậm chí không phải rời khỏi IDE để làm điều đó.
Greg Beech

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

Đầu ra là "Đã cố đọc bộ nhớ được bảo vệ. Đây là dấu hiệu cho thấy bộ nhớ khác bị hỏng."


1
Hấp dẫn! Âm thanh như một lỗi biên dịch, mặc dù; Tôi đã chuyển sang C # và nó hoạt động tốt. Điều đó nói rằng, có rất nhiều vấn đề với các ngoại lệ được đưa vào Load và nó hoạt động khác với / không có trình gỡ lỗi - bạn có thể bắt gặp với trình gỡ lỗi, nhưng không phải không có (trong một số trường hợp).
Marc Gravell

Xin lỗi, tôi quên, bạn cần thêm hộp tổ hợp vào biểu mẫu trước khi nó sẽ.
Joshua

Đây có phải là để khởi tạo hộp thoại bằng cách sử dụng SEH như một loại cơ chế giao tiếp nội bộ khủng khiếp? Tôi mơ hồ nhớ một cái gì đó như thế trong Win32.
Daniel Earwicker

1
Đây là vấn đề tương tự cbp ở trên. Giá trị được trả về là một bản sao, do đó, bất kỳ tham chiếu nào đến bất kỳ thuộc tính nào xuất phát từ bản sao nói trên đều hướng đến vùng đất bit bit ... byte.com/topic/net/answers/ trộm
Bennett Dill

1
Không. Không có cấu trúc ở đây. Tôi thực sự gỡ lỗi nó. Nó thêm một NULL vào bộ sưu tập mục danh sách của hộp tổ hợp riêng gây ra sự cố chậm trễ.
Joshua

10

PropertyInfo.SetValue () có thể gán ints cho enums, ints cho ints nullable, enum cho enum nullable, nhưng không ints cho enum 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 !!!

Mô tả đầy đủ ở đây


10

Điều gì xảy ra nếu bạn có một lớp chung có các phương thức có thể được làm mơ hồ tùy thuộc vào các đối số kiểu? Gần đây tôi gặp phải tình huống này khi viết một cuốn từ điển hai chiều. Tôi muốn viết các Get()phương thức đối xứng sẽ trả lại kết quả ngược lại với bất kỳ đối số nào được thông qua. Một cái gì đó như thế này:

class TwoWayRelationship<T1, T2>
{
    public T2 Get(T1 key) { /* ... */ }
    public T1 Get(T2 key) { /* ... */ }
}

Tất cả đều tốt nếu bạn tạo một ví dụ ở đâu T1T2là các loại khác nhau:

var r1 = new TwoWayRelationship<int, string>();
r1.Get(1);
r1.Get("a");

Nhưng nếu T1T2giống nhau (và có lẽ nếu một lớp con là lớp con khác), thì đó là lỗi trình biên dịch:

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

Thật thú vị, tất cả các phương pháp khác trong trường hợp thứ hai vẫn có thể sử dụng được; nó chỉ gọi phương thức không rõ ràng gây ra lỗi biên dịch. Trường hợp thú vị, nếu một chút không chắc chắn và tối nghĩa.


Những người phản đối quá tải phương pháp sẽ thích cái này ^^.
Christian Klauser

1
Tôi không biết, điều này hoàn toàn có ý nghĩa với tôi.
Scott Whitlock

10

C # Access Puzzler


Lớp dẫn xuất sau đây đang truy cập vào một trường riêng từ lớp cơ sở của nó và trình biên dịch âm thầm nhìn sang phía bên kia:

public class Derived : Base
{
    public int BrokenAccess()
    {
        return base.m_basePrivateField;
    }
}

Lĩnh vực này thực sự là riêng tư:

private int m_basePrivateField = 0;

Quan tâm để đoán làm thế nào chúng ta có thể biên dịch mã như vậy?

.

.

.

.

.

.

.

Câu trả lời


Bí quyết là khai báo Derivednhư là một lớp bên trong của Base:

public class Base
{
    private int m_basePrivateField = 0;

    public class Derived : Base
    {
        public int BrokenAccess()
        {
            return base.m_basePrivateField;
        }
    }
}

Các lớp bên trong được cấp quyền truy cập đầy đủ cho các thành viên lớp bên ngoài. Trong trường hợp này, lớp bên trong cũng xảy ra xuất phát từ lớp bên ngoài. Điều này cho phép chúng tôi "phá vỡ" sự đóng gói của các thành viên tư nhân.


Đó thực sự là tài liệu tốt; msdn.microsoft.com/en-us/l Library / ms173120% 28VS.80% 29.aspx . Nó có thể là một tính năng hữu ích đôi khi, đặc biệt nếu lớp bên ngoài là tĩnh.

Vâng - tất nhiên đó là tài liệu. Tuy nhiên, rất ít người giải được câu đố này nên tôi nghĩ đó là một câu đố hay.
Omer Mor

2
Có vẻ như bạn có khả năng tràn ngăn xếp rất mạnh bằng cách có một lớp bên trong kế thừa chủ sở hữu của nó ...
Jamie Treworgy

Một trường hợp tương tự (và hoàn toàn chính xác) khác là một đối tượng có thể truy cập một thành viên riêng của một đối tượng khác cùng loại:class A { private int _i; public void foo(A other) { int res = other._i; } }
Olivier Jacot-Descombes

10

Chỉ cần tìm thấy một điều tốt đẹp ngày hôm nay:

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

Điều này ném lỗi biên dịch.

Lệnh gọi phương thức 'Khởi tạo' cần được gửi động, nhưng không thể vì nó là một phần của biểu thức truy cập cơ sở. Xem xét việc truyền các đối số động hoặc loại bỏ quyền truy cập cơ sở.

Nếu tôi viết base.Initialize (thứ như đối tượng); nó hoạt động hoàn hảo, tuy nhiên đây dường như là một "từ ma thuật" ở đây, vì nó hoàn toàn giống nhau, mọi thứ vẫn được nhận là động ...


8

Trong một API chúng tôi đang sử dụng, các phương thức trả về một đối tượng miền có thể trả về một "đối tượng null" đặc biệt. Trong quá trình thực hiện điều này, toán tử so sánh và Equals()phương thức được ghi đè để trả về truenếu nó được so sánh với null.

Vì vậy, người dùng API này có thể có một số mã như thế này:

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

hoặc có lẽ dài dòng hơn một chút, như thế này:

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

trong đó GetDefault()một phương thức trả về một số giá trị mặc định mà chúng ta muốn sử dụng thay vì null. Điều bất ngờ đã xảy ra với tôi khi tôi đang sử dụng ReSharper và làm theo khuyến nghị để viết lại một trong những điều sau đây:

return test ?? GetDefault();

Nếu đối tượng thử nghiệm là đối tượng null được trả về từ API thay vì đúng null, hành vi của mã hiện đã thay đổi, vì toán tử hợp nhất null thực sự kiểm tra null, không chạy operator=hoặc Equals().


1
không thực sự ac # trường hợp góc, nhưng chúa ơi ai nghĩ rằng lên?!?
Ray Booysen

Không phải mã này chỉ sử dụng các loại nullable? Do đó ReSharper đề xuất "??" sử dụng. Như Ray đã nói, tôi sẽ không nghĩ đây là một vụ án góc; hoặc là tôi sai?
Tony

1
Có, các loại là nullable - ngoài ra còn có NullObject. Nếu đó là trường hợp góc, tôi không biết, nhưng ít nhất đó là trường hợp 'if (a! = Null) trả về a; trở lại b; ' không giống như 'trả lại a ?? b '. Tôi hoàn toàn đồng ý rằng đó là một vấn đề với thiết kế khung / API - quá tải == null để trả về đúng trên một đối tượng chắc chắn không phải là ý kiến ​​hay!
Tor Livar

8

Hãy xem xét trường hợp kỳ lạ này:

public interface MyInterface {
  void Method();
}
public class Base {
  public void Method() { }
}
public class Derived : Base, MyInterface { }

Nếu BaseDerivedđược khai báo trong cùng một cụm, trình biên dịch sẽ tạo Base::Methodảo và niêm phong (trong CIL), mặc dùBase không thực hiện giao diện.

Nếu BaseDerivedở trong các hội đồng khác nhau, khi biên dịch tập hợp Derived, trình biên dịch sẽ không thay đổi tập hợp khác, vì vậy nó sẽ giới thiệu một thành viên trong Derivedđó sẽ là một triển khai rõ ràng cho việc MyInterface::Methodđó sẽ ủy thác cuộc gọi Base::Method.

Trình biên dịch phải làm điều này để hỗ trợ công văn đa hình liên quan đến giao diện, tức là nó phải làm cho phương thức đó trở nên ảo.


Điều đó thực sự nghe có vẻ kỳ quặc. Sẽ phải điều tra sau :)
Jon Skeet

@Jon Skeet: Tôi đã tìm thấy điều này trong khi nghiên cứu các chiến lược thực hiện cho các vai trò trong C # . Sẽ là tuyệt vời để có được phản hồi của bạn về điều đó!
Jordão

7

Sau đây có thể là kiến ​​thức chung tôi chỉ đơn giản là thiếu, nhưng eh. Thời gian trước, chúng tôi đã có một trường hợp lỗi bao gồm các thuộc tính ảo. Tóm tắt bối cảnh một chút, xem xét đoạn mã sau và áp dụng điểm dừng cho khu vực được chỉ định:

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

Khi ở trong Derivedbối cảnh đối tượng, bạn có thể có hành vi tương tự khi thêm base.Propertydưới dạng đồng hồ hoặc nhậpbase.Property vào quickwatch.

Mất một thời gian để tôi nhận ra những gì đang xảy ra. Cuối cùng, tôi đã được khai sáng bởi Quickwatch. Khi đi vào Quickwatch và khám phá Derivedđối tượng d (hoặc từ bối cảnh của đối tượng this) và chọn trườngbase , trường chỉnh sửa trên đầu Quickwatch sẽ hiển thị các biểu tượng sau:

((TestProject1.Base)(d))

Điều đó có nghĩa là nếu cơ sở được thay thế như vậy, cuộc gọi sẽ là

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

đối với Đồng hồ, Quickwatch và các công cụ gỡ lỗi trên chuột, và sau đó nó sẽ có ý nghĩa cho nó hiển thị "AWESOME"thay vì "BASE_AWESOME"khi xem xét tính đa hình. Tôi vẫn không chắc tại sao nó lại biến nó thành diễn viên, một giả thuyết cho rằngcall có thể không có sẵn từ bối cảnh của các mô-đun đó, và chỉ callvirt.

Dù sao đi nữa, điều đó rõ ràng không làm thay đổi bất cứ điều gì về chức năng, Derived.BasePropertyvẫn sẽ thực sự quay trở lại "BASE_AWESOME", và do đó đây không phải là gốc rễ của lỗi tại nơi làm việc, chỉ đơn giản là một thành phần khó hiểu. Tuy nhiên tôi đã thấy thú vị khi nó có thể đánh lừa các nhà phát triển, những người sẽ không biết về thực tế đó trong các phiên gỡ lỗi của họ, đặc biệt nếu Basekhông được tiết lộ trong dự án của bạn mà chỉ được gọi là DLL của bên thứ 3, dẫn đến Devs chỉ nói:

"Oi, đợi đã..cái gì vậy? Omg là DLL như thế nào .. làm gì đó buồn cười"


Điều đó không có gì đặc biệt, đó chỉ là cách ghi đè công việc.
cấu hình

7

Điều này khá khó để lên đỉnh. Tôi đã gặp phải nó trong khi tôi đang cố gắng xây dựng một triển khai RealProxy thực sự hỗ trợ Begin / EndInvoke (cảm ơn MS vì đã làm điều này không thể thực hiện được nếu không có những vụ hack khủng khiếp). Ví dụ này về cơ bản là một lỗi trong CLR, đường dẫn mã không được quản lý cho BeginInvoke không xác thực rằng thông báo trả về từ RealProxy.PrivateInvoke (và ghi đè Invoke của tôi) sẽ trả về một thể hiện của IAsyncResult. Khi nó trở lại, CLR vô cùng bối rối và mất bất kỳ ý tưởng nào về những gì đang diễn ra, như đã được chứng minh bằng các thử nghiệm ở phía dưới.

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

Đầu ra:

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

Tôi không chắc liệu bạn có nói đây là một sự kỳ quặc của Windows Vista / 7 hay .Net không, nhưng nó đã khiến tôi phải gãi đầu một lúc.

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."

Trong Windows Vista / 7, tập tin thực sự sẽ được ghi vào C:\Users\<username>\Virtual Store\Program Files\my folder\test.txt


2
Đây thực sự là một cải tiến bảo mật vista (không phải 7, afaik). Nhưng điều thú vị là bạn có thể đọc và mở tệp bằng đường dẫn tệp chương trình, trong khi nếu bạn nhìn vào đó với explorer thì không có gì. Điều này khiến tôi mất gần một ngày làm việc @ một khách hàng trước khi cuối cùng tôi phát hiện ra nó.
Henri

Đó chắc chắn là một điều Windows 7 quá. Đó là những gì tôi đã sử dụng khi tôi gặp phải nó. Tôi hiểu lý do đằng sau nó nhưng nó vẫn bực bội để tìm ra.
Spencer Ruport

Trong Vista / Win 7 (winXP về mặt kỹ thuật cũng vậy), các ứng dụng nên ghi vào thư mục AppData trong vùng thư mục Người dùng, dưới dạng dữ liệu người dùng về mặt kỹ thuật. Các ứng dụng không nên ghi vào chương trình / windows / system32 / etc, trừ khi chúng có quyền riêng tư của quản trị viên và những quyền riêng tư đó chỉ nên ở đó để nói nâng cấp chương trình / gỡ cài đặt / cài đặt tính năng mới. NHƯNG! Vẫn không ghi vào system32 / windows / etc :) Nếu bạn chạy mã ở trên với tư cách quản trị viên (nhấp chuột phải> chạy với tư cách quản trị viên) thì về mặt lý thuyết nên ghi vào thư mục ứng dụng tệp chương trình.
Steve Syfuhs


6

Bạn đã bao giờ nghĩ trình biên dịch C # có thể tạo CIL không hợp lệ chưa? Chạy cái này và bạn sẽ nhận được 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));
  }
}

Tôi không biết làm thế nào giá vé trong trình biên dịch C # 4.0.

EDIT : đây là đầu ra từ hệ thống của tôi:

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]

Hoạt động với tôi với cả trình biên dịch C # 3.5 và trình biên dịch C # 4 ...
Jon Skeet

Trong hệ thống của tôi, nó không hoạt động. Tôi sẽ dán đầu ra trong câu hỏi.
Jordão

Nó đã thất bại với tôi trong .NET 3.5 (không có thời gian để kiểm tra 4.0). Và tôi có thể sao chép vấn đề với mã VB.NET.
Đánh dấu Hurd

3

Có một cái gì đó thực sự thú vị về C #, cách nó xử lý các lần đóng cửa.

Thay vì sao chép các giá trị biến stack vào biến free free, nó thực hiện phép thuật tiền xử lý đó bao bọc tất cả các lần xuất hiện của biến vào một đối tượng và do đó di chuyển nó ra khỏi stack - thẳng đến đống! :)

Tôi đoán, điều đó làm cho ngôn ngữ C # thậm chí còn đầy đủ chức năng hơn (hoặc lambda-Complete huh)) so với chính ML (sử dụng sao chép giá trị ngăn xếp AFAIK). F # cũng có tính năng đó, giống như C #.

Điều đó mang lại nhiều niềm vui cho tôi, cảm ơn các bạn MS!

Tuy nhiên, đây không phải là trường hợp kỳ quặc hay góc cạnh ... nhưng có gì đó thực sự bất ngờ từ ngôn ngữ VM dựa trên ngăn xếp :)


3

Từ một câu hỏi tôi đã hỏi cách đây không lâu:

Toán tử có điều kiện có thể bỏ mặc?

Được:

Bool aBoolValue;

Trường hợp aBoolValueđược gán hoặc Đúng hoặc Sai;

Sau đây sẽ không biên dịch:

Byte aByteValue = aBoolValue ? 1 : 0;

Nhưng điều này sẽ:

Int anIntValue = aBoolValue ? 1 : 0;

Câu trả lời được cung cấp là khá tốt quá.


mặc dù tôi ve not test it Ichắc chắn rằng điều này sẽ hoạt động: Byte aByteValue = aBoolValue? (Byte) 1: (Byte) 0; Hoặc: Byte aByteValue = (Byte) (aBoolValue? 1: 0);
Alex Pacurar

2
Vâng, Alex, điều đó sẽ làm việc. Chìa khóa là trong đúc ngầm. 1 : 0một mình sẽ ngầm chuyển sang int, không phải Byte.
MPelletier

2

Phạm vi trong c # đôi khi thực sự kỳ quái. Hãy cho tôi một ví dụ:

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

OleDbCommand command = SQLServer.CreateCommand();

Điều này không biên dịch, vì lệnh được khai báo lại? Có một số phỏng đoán thú vị về lý do tại sao nó hoạt động theo cách này trong luồng này trên stackoverflow và trong blog của tôi .


34
Tôi không xem đó là đặc biệt kỳ quái. Những gì bạn gọi là "mã hoàn toàn chính xác" trong blog của bạn là hoàn toàn không chính xác theo đặc điểm kỹ thuật ngôn ngữ. Nó có thể đúng trong một số ngôn ngữ tưởng tượng bạn muốn như C # được, nhưng spec ngôn ngữ là khá rõ ràng rằng trong C # nó không hợp lệ.
Jon Skeet

7
Vâng, nó là hợp lệ trong C / C ++. Và vì nó là C # nên tôi vẫn thích nó vẫn hoạt động. Điều khiến tôi băn khoăn nhất là không có lý do gì để trình biên dịch làm điều này. Không khó để làm phạm vi lồng nhau. Tôi đoán tất cả đều thuộc về yếu tố ít bất ngờ nhất. Có nghĩa là nó có thể là thông số kỹ thuật nói điều này và điều đó, nhưng điều đó không thực sự giúp tôi rất nhiều nếu nó hoàn toàn phi logic rằng nó hành xử theo cách đó.
Anders Rune Jensen

6
C #! = C / C ++. Bạn có muốn sử dụng cout << "Hello World!" << kết thúc; thay vì Console.WriteLine ("Xin chào thế giới!");? Ngoài ra nó không phi logic, chỉ cần đọc thông số kỹ thuật.
Kredns

9
Tôi đang nói về các quy tắc phạm vi là một phần cốt lõi của ngôn ngữ. Bạn đang nói về thư viện tiêu chuẩn. Nhưng giờ đây rõ ràng với tôi rằng tôi chỉ cần đọc đặc tả nhỏ của ngôn ngữ c # trước khi tôi bắt đầu lập trình trong đó.
Anders Rune Jensen

6
Eric Lippert thực sự đã đăng những lý do tại sao C # được thiết kế như vậy gần đây: blog.msdn.com/ericlippert/archive/2009/11/02/ . Tóm tắt là vì ít có khả năng thay đổi sẽ có hậu quả không lường trước được.
Helephant
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.