Cách di chuyển suy nghĩ của tôi từ C ++ sang C #


12

Tôi là một nhà phát triển C ++ có kinh nghiệm, tôi biết ngôn ngữ rất chi tiết và đã sử dụng một số tính năng cụ thể của nó một cách sâu sắc. Ngoài ra, tôi biết các nguyên tắc của OOD và các mẫu thiết kế. Bây giờ tôi đang học C # nhưng tôi không thể ngăn được cảm giác không thể thoát khỏi suy nghĩ về C ++. Tôi đã tự trói mình rất mạnh vào những điểm mạnh của C ++ đến nỗi tôi không thể sống thiếu một số tính năng. Và tôi không thể tìm thấy bất kỳ cách giải quyết hay thay thế nào cho chúng trong C #.

Những cách thực hành tốt , mẫu thiết kế , thành ngữ khác nhau trong C # từ quan điểm C ++ bạn có thể đề xuất? Làm thế nào để có được một thiết kế C ++ hoàn hảo không bị câm trong C #?

Cụ thể, tôi không thể tìm ra cách giải quyết C # tốt (ví dụ gần đây):

  • Kiểm soát thời gian tồn tại của tài nguyên yêu cầu dọn dẹp xác định (như tệp). Điều này dễ dàng có usingtrong tay nhưng làm thế nào để sử dụng nó đúng cách khi quyền sở hữu tài nguyên đang được chuyển [... chủ đề betwen]? Trong C ++, tôi chỉ đơn giản là sử dụng các con trỏ được chia sẻ và để nó xử lý 'bộ sưu tập rác' đúng lúc.
  • Liên tục vật lộn với các chức năng ghi đè cho các tổng quát cụ thể (Tôi thích những thứ như chuyên môn mẫu một phần trong C ++). Tôi có nên từ bỏ mọi nỗ lực để thực hiện bất kỳ chương trình chung nào trong C # không? Có lẽ thuốc generic bị hạn chế về mục đích và không phải là C # -ish sử dụng chúng ngoại trừ một miền cụ thể của vấn đề?
  • Chức năng giống như macro. Mặc dù nói chung là một ý tưởng tồi, đối với một số miền của các vấn đề không có cách giải quyết nào khác (ví dụ: đánh giá có điều kiện của một tuyên bố, như với các nhật ký chỉ nên chuyển đến các bản phát hành Debug). Không có chúng đồng nghĩa với việc tôi cần đặt thêm if (condition) {...}nồi hơi và nó vẫn không bằng về mặt kích hoạt tác dụng phụ.

# 1 là một khó khăn đối với tôi, muốn biết câu trả lời cho mình. # 2 - bạn có thể đưa ra một ví dụ mã C ++ đơn giản và giải thích lý do tại sao bạn cần điều đó không? Đối với # 3 - sử dụng C#để tạo mã khác. Bạn có thể đọc một CSVhoặc một XMLhoặc những gì bạn đã gửi làm đầu vào và tạo một C#hoặc một SQLtệp. Điều này có thể mạnh hơn so với sử dụng các macro chức năng.
Công việc

Về chủ đề chức năng giống như macro, bạn đang cố gắng làm những việc cụ thể nào? Những gì tôi đã tìm thấy là thường có cấu trúc ngôn ngữ C # để xử lý những gì tôi đã làm với một macro trong C ++. Ngoài ra, hãy xem các chỉ thị tiền xử lý C #: msdn.microsoft.com/en-us/l
Library / 4y6tbswk.aspx

Nhiều thứ được thực hiện với macro trong C ++ có thể được thực hiện với sự phản chiếu trong C #.
Sebastian Redl

Đó là một trong những thú cưng của tôi khi ai đó nói "Công cụ phù hợp cho công việc phù hợp - chọn ngôn ngữ dựa trên những gì bạn sẽ cần." Đối với các chương trình lớn trong các ngôn ngữ có mục đích chung, đây là một tuyên bố vô ích, không có ích. Điều đó nói rằng, điều mà C ++ giỏi hơn hầu hết các ngôn ngữ khác là quản lý tài nguyên xác định. Là quản lý tài nguyên xác định là một tính năng chính của chương trình của bạn, hoặc một cái gì đó bạn đã sử dụng? Nếu đó không phải là một tính năng quan trọng của người dùng, bạn có thể tập trung quá mức vào đó.
Ben

Câu trả lời:


16

Theo kinh nghiệm của tôi, không có cuốn sách để làm điều này. Diễn đàn giúp đỡ với thành ngữ và thực tiễn tốt nhất, nhưng cuối cùng bạn sẽ phải đặt thời gian. Thực hành bằng cách giải quyết một số vấn đề khác nhau trong C #. Hãy nhìn vào những gì khó khăn / vụng về và cố gắng làm cho chúng bớt khó khăn và bớt khó xử hơn vào lần tới. Đối với tôi, quá trình này mất khoảng một tháng trước khi tôi "hiểu".

Điều lớn nhất để tập trung vào là thiếu quản lý bộ nhớ. Trong C ++, điều này sẽ vượt qua mọi quyết định thiết kế của bạn, nhưng trong C # thì nó lại phản tác dụng. Trong C #, bạn thường muốn bỏ qua cái chết hoặc chuyển trạng thái khác của các đối tượng của bạn. Đó là loại liên kết thêm khớp nối không cần thiết.

Chủ yếu, tập trung vào việc sử dụng các công cụ mới một cách hiệu quả nhất, không đánh bại bản thân bạn trên một tập tin đính kèm với những công cụ cũ.

Làm thế nào để có được một thiết kế C ++ hoàn hảo không bị câm trong C #?

Bằng cách nhận ra rằng các thành ngữ khác nhau thúc đẩy các phương pháp thiết kế khác nhau. Bạn không thể chuyển một cái gì đó 1: 1 giữa (hầu hết) các ngôn ngữ và mong đợi một cái gì đó đẹp và thành ngữ ở đầu bên kia.

Nhưng để đáp ứng với các kịch bản cụ thể:

Chia sẻ tài nguyên không được quản lý - Đây là một ý tưởng tồi trong C ++ và cũng là một ý tưởng tồi trong C #. Nếu bạn có một tập tin thì hãy mở nó, sử dụng nó và đóng nó lại. Chia sẻ nó giữa các luồng chỉ là vấn đề rắc rối, và nếu chỉ có một luồng thực sự sử dụng nó thì hãy mở luồng đó / sở hữu / đóng nó. Nếu bạn thực sự có một tệp tồn tại lâu vì một số lý do, thì hãy tạo một lớp để thể hiện điều đó và để ứng dụng quản lý khi cần thiết (đóng trước khi thoát, liên kết chặt chẽ với loại sự kiện Đóng ứng dụng, v.v.)

Chuyên môn hóa một phần mẫu - Bạn không gặp may ở đây, mặc dù tôi càng sử dụng C #, tôi càng thấy việc sử dụng mẫu tôi đã làm trong C ++ để rơi vào YAGNI. Tại sao làm một cái gì đó chung chung khi T luôn là một int? Các kịch bản khác được giải quyết tốt nhất trong C # bằng cách áp dụng giao diện tự do. Bạn không cần chung chung khi một loại giao diện hoặc đại biểu có thể gõ mạnh những gì bạn muốn thay đổi.

Điều kiện tiền xử lý - C # có #if, đi hạt. Tốt hơn nữa, nó có các thuộc tínhđiều kiện sẽ đơn giản loại bỏ hàm đó khỏi mã nếu không xác định biểu tượng trình biên dịch.


Về quản lý tài nguyên, trong C #, việc có các lớp trình quản lý (có thể là tĩnh hoặc động) là khá phổ biến.
rwong

Về tài nguyên được chia sẻ: Những tài nguyên được quản lý dễ dàng trong C # (nếu điều kiện chủng tộc không liên quan), những tài nguyên không được quản lý sẽ khó khăn hơn nhiều.
Ded repeatator

@rwong - phổ biến, nhưng các lớp người quản lý vẫn là một mô hình chống cần tránh.
Telastyn

Liên quan đến quản lý bộ nhớ, tôi thấy rằng việc chuyển sang C # khiến việc này trở nên khó khăn hơn, đặc biệt là vào đầu. Tôi nghĩ bạn phải nhận thức rõ hơn trong C ++. Trong C ++, bạn biết chính xác thời gian và địa điểm mà mọi đối tượng được phân bổ, phân bổ và tham chiếu. Trong C #, đây là tất cả ẩn từ bạn. Nhiều lần trong C # bạn thậm chí không nhận ra rằng đã tham chiếu đến một đối tượng và bộ nhớ bắt đầu bị rò rỉ. Vui vẻ theo dõi rò rỉ bộ nhớ trong C #, thường không quan trọng để tìm ra trong C ++. Tất nhiên, với kinh nghiệm bạn học được những sắc thái này của C # để chúng có xu hướng trở thành ít vấn đề hơn.
Dunk

4

Theo như tôi có thể thấy, điều duy nhất cho phép Quản lý trọn đời xác định là IDisposable, nó bị hỏng (như: không hỗ trợ ngôn ngữ hoặc loại hệ thống) khi, như bạn nhận thấy, bạn cần Chuyển quyền sở hữu tài nguyên đó.

Ở cùng thuyền với bạn - Nhà phát triển C ++ đang làm C # atm. - việc thiếu hỗ trợ để mô hình Chuyển quyền sở hữu (tệp, kết nối db, ...) trong các loại / chữ ký C # đã gây khó chịu cho tôi, nhưng tôi phải thừa nhận rằng nó hoạt động khá ổn khi "chỉ" dựa vào về quy ước và tài liệu API để có quyền này.

  • Sử dụng IDisposableđể làm sạch lên xác định khi và chỉ khi cần thiết.
  • Khi bạn cần chuyển quyền sở hữu của một IDisposable, chỉ cần đảm bảo API liên quan rõ ràng về điều này và không sử dụng usingtrong trường hợp này, vì usingkhông thể "hủy bỏ" để nói.

Đúng, nó không thanh lịch như những gì bạn có thể thể hiện trong hệ thống loại với C ++ hiện đại (di chuyển sơ đồ, v.v.), nhưng nó đã hoàn thành công việc, và (thật ngạc nhiên với tôi) cộng đồng C # dường như không quan tâm quá mức, AFAICT.


2

Bạn đã đề cập đến hai tính năng C ++ cụ thể. Tôi sẽ thảo luận về RAII:

Nó thường không được áp dụng, vì C # là rác được thu thập. Cấu trúc C # gần nhất có lẽ là IDisposablegiao diện, được sử dụng như sau:

using (FileStream fs = File.OpenRead(@"c:\path\filename.txt"))
{
   //Do stuff with the file.
} //File will be closed here.

Bạn không nên mong đợi thực hiện IDisposablethường xuyên (và làm như vậy là hơi tiên tiến), vì nó không cần thiết cho các tài nguyên được quản lý. Tuy nhiên, bạn nên sử dụng usingcấu trúc (hoặc Disposetự gọi mình, nếu usingkhông khả thi) cho bất cứ điều gì thực hiện IDisposable. Ngay cả khi bạn làm hỏng điều này, các lớp C # được thiết kế tốt sẽ cố gắng xử lý việc này cho bạn (sử dụng các công cụ hoàn thiện). Tuy nhiên, trong ngôn ngữ được thu gom rác, mẫu RAII không hoạt động hoàn toàn (vì bộ sưu tập không mang tính quyết định), do đó việc dọn dẹp tài nguyên của bạn sẽ bị trì hoãn (đôi khi rất thảm khốc).


RAII là rắc rối lớn nhất của tôi thực sự. Giả sử tôi có một tài nguyên (giả sử một tệp) được tạo bởi một luồng và được trao cho một luồng khác. Làm thế nào tôi có thể chắc chắn rằng nó được xử lý tại một thời điểm nhất định khi chủ đề đó không cần nó nữa? Tất nhiên, tôi có thể gọi Dispose nhưng sau đó nó trông xấu hơn các con trỏ được chia sẻ trong C ++. Không có gì của RAII trong đó.
Andrew

@Andrew Những gì bạn mô tả dường như ngụ ý chuyển quyền sở hữu, vì vậy bây giờ luồng thứ hai chịu trách nhiệm về Disposing()tệp và có lẽ nên sử dụng using.
Svick

@Andrew - Nói chung, bạn không nên làm điều đó. Thay vào đó, bạn nên nói với chủ đề làm thế nào để có được tập tin và để nó có quyền sở hữu trọn đời.
Telastyn

1
@Telastyn Điều đó có nghĩa là việc trao quyền sở hữu đối với tài nguyên được quản lý là vấn đề lớn hơn trong C # so với C ++? Khá ngạc nhiên.
Andrew

6
@Andrew: Hãy để tôi tóm tắt: trong C ++, bạn có được sự thống nhất, quản lý bán tự động tất cả các tài nguyên. Trong C #, bạn có thể quản lý bộ nhớ hoàn toàn tự động - và quản lý hoàn toàn thủ công mọi thứ khác. Không có thay đổi thực sự, vì vậy câu hỏi thực sự duy nhất của bạn không phải là "làm thế nào để tôi sửa lỗi này?", Chỉ "làm thế nào để tôi quen với điều này?"
Jerry Coffin

2

Đây là một câu hỏi rất hay, vì tôi đã thấy một số nhà phát triển C ++ viết mã C # khủng.

Tốt nhất không nên nghĩ về C # là "C ++ với cú pháp đẹp hơn". Chúng là những ngôn ngữ khác nhau đòi hỏi cách tiếp cận khác nhau trong suy nghĩ của bạn. C ++ buộc bạn phải liên tục suy nghĩ về những gì CPU và bộ nhớ sẽ làm. C # không như thế. C # đặc biệt được thiết kế để bạn không nghĩ về CPU và bộ nhớ và thay vào đó đang nghĩ về lĩnh vực kinh doanh mà bạn đang viết .

Một ví dụ về điều này mà tôi đã thấy là rất nhiều nhà phát triển C ++ sẽ thích sử dụng cho các vòng lặp vì chúng nhanh hơn so với foreach. Đây thường là một ý tưởng tồi trong C # vì nó hạn chế các loại có thể của bộ sưu tập được lặp đi lặp lại (và do đó khả năng sử dụng lại và tính linh hoạt của mã).

Tôi nghĩ rằng cách tốt nhất để điều chỉnh từ C ++ sang C # là thử và tiếp cận mã hóa từ một góc nhìn khác. Lúc đầu, việc này sẽ khó khăn, vì qua nhiều năm, bạn sẽ hoàn toàn quen với việc sử dụng luồng "CPU và bộ nhớ đang làm gì" trong não để lọc mã bạn viết. Nhưng trong C #, thay vào đó bạn nên suy nghĩ về mối quan hệ giữa các đối tượng trong lĩnh vực kinh doanh. "Tôi muốn làm gì" trái ngược với "máy tính đang làm gì".

Nếu bạn muốn làm một cái gì đó cho mọi thứ trong danh sách các đối tượng, thay vì viết một vòng lặp for trong danh sách, hãy tạo một phương thức lấy IEnumerable<MyObject>và sử dụng một foreachvòng lặp.

Ví dụ cụ thể của bạn:

Kiểm soát thời gian tồn tại của tài nguyên yêu cầu dọn dẹp xác định (như tệp). Điều này thật dễ dàng khi sử dụng trong tay nhưng làm thế nào để sử dụng nó đúng cách khi quyền sở hữu tài nguyên đang được chuyển [... betwen thread]? Trong C ++, tôi chỉ cần sử dụng các con trỏ được chia sẻ và để nó xử lý 'bộ sưu tập rác' vào đúng thời điểm.

Bạn không bao giờ nên làm điều này trong C # (đặc biệt là giữa các luồng). Nếu bạn cần làm một cái gì đó cho một tập tin, hãy làm nó ở một nơi cùng một lúc. Sẽ ổn (và thực tế là tốt) để viết một lớp bao bọc quản lý tài nguyên không được quản lý được chuyển qua giữa các lớp của bạn, nhưng đừng thử và chuyển một tệp giữa các luồng và có các lớp riêng biệt viết / đọc / đóng / mở nó. Đừng chia sẻ quyền sở hữu tài nguyên không được quản lý. Sử dụng Disposemô hình để đối phó với dọn dẹp.

Liên tục vật lộn với các chức năng ghi đè cho các tổng quát cụ thể (Tôi thích những thứ như chuyên môn mẫu một phần trong C ++). Tôi có nên từ bỏ mọi nỗ lực để thực hiện bất kỳ chương trình chung nào trong C # không? Có lẽ thuốc generic bị hạn chế về mục đích và không phải là C # -ish sử dụng chúng ngoại trừ một miền cụ thể của vấn đề?

Generics trong C # được thiết kế chung chung. Các chuyên ngành của thuốc generic nên được xử lý bởi các lớp dẫn xuất. Tại sao một List<T>hành vi khác nhau nếu nó là một List<int>hoặc một List<string>? Tất cả các hoạt động trên List<T>là chung chung để áp dụng cho bất kỳ List<T> . Nếu bạn muốn thay đổi hành vi của .Addphương thức trên a List<string>, thì hãy tạo một lớp dẫn xuất MySpecializedStringCollection : List<string>hoặc một lớp MySpecializedStringCollection : IList<string>tổng hợp sử dụng chung chung bên trong nhưng thực hiện mọi thứ theo cách khác. Điều này giúp bạn tránh vi phạm Nguyên tắc thay thế Liskov và thực sự lừa đảo những người khác sử dụng lớp học của bạn.

Chức năng giống như macro. Mặc dù nói chung là một ý tưởng tồi, đối với một số miền của các vấn đề không có cách giải quyết nào khác (ví dụ: đánh giá có điều kiện của một tuyên bố, như với các nhật ký chỉ nên chuyển đến các bản phát hành Debug). Không có chúng đồng nghĩa với việc tôi cần đặt thêm nếu (điều kiện) {...} nồi hơi và nó vẫn không bằng về mặt kích hoạt tác dụng phụ.

Như những người khác đã nói, bạn có thể sử dụng các lệnh tiền xử lý để làm điều này. Các thuộc tính thậm chí còn tốt hơn. Trong các thuộc tính chung là cách tốt nhất để xử lý những thứ không phải là chức năng "cốt lõi" cho một lớp.

Nói tóm lại, khi viết C #, hãy nhớ rằng bạn nên suy nghĩ hoàn toàn về lĩnh vực kinh doanh chứ không phải về CPU và bộ nhớ. Bạn có thể tối ưu hóa sau nếu cần , nhưng mã của bạn sẽ phản ánh mối quan hệ giữa các nguyên tắc kinh doanh mà bạn đang cố gắng lập bản đồ.


3
WRT. chuyển tài nguyên: "bạn không bao giờ nên làm điều này" không cắt nó IMHO. Thông thường, một thiết kế rất tốt đề xuất chuyển một IDisposable( không phải tài nguyên thô) từ lớp này sang lớp khác: Chỉ cần xem API StreamWriter nơi điều này có ý nghĩa hoàn hảo với Dispose()luồng đã truyền. Và có một lỗ hổng trong ngôn ngữ C # - bạn không thể diễn đạt điều này trong hệ thống loại.
Martin Ba

2
"Một ví dụ về điều này mà tôi đã thấy là rất nhiều nhà phát triển C ++ sẽ thích sử dụng cho các vòng lặp vì chúng nhanh hơn so với foreach." Đây là một ý tưởng tồi trong C ++. Sự trừu tượng hóa không chi phí là sức mạnh to lớn của C ++ và foreach hoặc các thuật toán khác không nên chậm hơn một vòng lặp viết tay trong hầu hết các trường hợp.
Sebastian Redl

1

Điều khác biệt nhất đối với tôi là xu hướng làm mới mọi thứ . Nếu bạn muốn một đối tượng, hãy quên cố gắng truyền nó vào một thói quen và chia sẻ dữ liệu cơ bản, có vẻ như mẫu C # chỉ là để tạo cho mình một đối tượng mới. Điều này, nói chung, dường như là nhiều hơn theo cách C #. Lúc đầu, mẫu này có vẻ rất không hiệu quả, nhưng tôi đoán việc tạo ra hầu hết các đối tượng trong C # là một thao tác nhanh.

Tuy nhiên, cũng có các lớp tĩnh thực sự làm tôi khó chịu vì thường thì bạn cố gắng tạo một lớp chỉ để thấy bạn phải sử dụng nó. Tôi thấy nó không dễ dàng để biết nên sử dụng.

Tôi khá vui khi tham gia chương trình C # để bỏ qua nó khi tôi không còn cần nó nữa, nhưng chỉ cần nhớ đối tượng đó chứa gì - nói chung bạn chỉ phải suy nghĩ nếu nó có một số dữ liệu 'bất thường', các đối tượng chỉ bao gồm bộ nhớ sẽ chỉ cần tự đi, những người khác sẽ phải được xử lý hoặc bạn sẽ phải xem cách tiêu diệt chúng - bằng tay hoặc bằng cách sử dụng một khối nếu không.

Mặc dù vậy, bạn sẽ nhanh chóng chọn nó, C # hầu như không khác với VB vì vậy bạn có thể coi nó là cùng một công cụ RAD (nếu nó giúp nghĩ về C # như VB.NET thì hãy làm như vậy, cú pháp chỉ hơi khác nhau, và ngày xưa sử dụng VB đã đưa tôi vào tư duy đúng đắn cho sự phát triển RAD). Khó khăn duy nhất là xác định bit nào của các thư viện .net khổng lồ mà bạn nên sử dụng. Google chắc chắn là bạn của bạn ở đây!


1
Truyền một đối tượng đến một thói quen để chia sẻ dữ liệu cơ bản là hoàn toàn tốt trong C #, ít nhất là từ góc độ cú pháp.
Brian
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.