C # có thuộc tính mở rộng không?


769

C # có thuộc tính mở rộng không?

Ví dụ: tôi có thể thêm thuộc tính mở rộng để DateTimeFormatInfogọi ShortDateLongTimeFormatcái nào sẽ trả về ShortDatePattern + " " + LongTimePatternkhông?


14
Tôi muốn thêm một phương thức mở rộng có tên IsNull trên Nullable <T> mà sẽ trả về! HasValue. .IsNull () chắc chắn kém xinh hơn .IsNull
Ken

1
Tôi thấy điều này hữu ích cho nhà điều hành ba?
PedroC88

2
Tôi muốn cái này bắt chước các Java enumcó thể có các thuộc tính và phương thức. Các C # enumkhông thể có các thuộc tính hoặc phương thức, nhưng bạn có thể tạo các phương thức mở rộng trên chúng. Câu hỏi này rất hữu ích với tôi và không nên đóng lại.
Ian McLaird

Mặc dù, như nhiều người đã nói, hiện tại không có kế hoạch nào để thêm ngôn ngữ này vào ngôn ngữ, không có lý do gì nó không thể được thực hiện. Thực tế là F # không chỉ có các thuộc tính mở rộng mà cả các phần mở rộng tĩnh cũng chứng minh rằng đó ít nhất là một ý tưởng tốt.
Richiban

2
Nên có một
Rootel

Câu trả lời:


366

Hiện tại, nó vẫn chưa được hỗ trợ bởi trình biên dịch Roslyn ...

Cho đến hiện tại, các thuộc tính mở rộng không được coi là đủ giá trị để được đưa vào các phiên bản trước của tiêu chuẩn C #. C # 7C # 8.0 đã xem đây là nhà vô địch đề xuất nhưng nó chưa được phát hành, hầu hết bởi vì ngay cả khi đã có một triển khai, họ muốn thực hiện ngay từ đầu.

Nhưng nó sẽ ...

Có một mục thành viên mở rộng trong danh sách công việc C # 7 để có thể được hỗ trợ trong tương lai gần. Trạng thái hiện tại của thuộc tính mở rộng có thể được tìm thấy trên Github trong mục liên quan .

Tuy nhiên, có một chủ đề thậm chí còn hứa hẹn hơn đó là "mở rộng mọi thứ" với trọng tâm là các thuộc tính đặc biệt và các lớp tĩnh hoặc thậm chí các trường.

Hơn nữa, bạn có thể sử dụng một cách giải quyết

Như được chỉ định trong bài viết này , bạn có thể sử dụng TypeDescriptorkhả năng để đính kèm một thuộc tính vào một thể hiện đối tượng trong thời gian chạy. Tuy nhiên, nó không sử dụng cú pháp của các thuộc tính tiêu chuẩn.
Nó hơi khác một chút so với chỉ đường cú pháp thêm khả năng xác định một thuộc tính mở rộng như
string Data(this MyClass instance)là bí danh cho phương thức mở rộng
string GetData(this MyClass instance)khi nó lưu trữ dữ liệu vào lớp.

Tôi hy vọng rằng C # 7 sẽ cung cấp một tiện ích mở rộng đầy đủ tính năng mọi thứ (thuộc tính và trường), tuy nhiên vào thời điểm đó, chỉ có thời gian mới trả lời.

Và hãy thoải mái đóng góp vì phần mềm của ngày mai sẽ đến từ cộng đồng.

Cập nhật: tháng 8 năm 2016

Khi nhóm dotnet công bố những gì mới trong C # 7.0 và từ một bình luận của Mads Torgensen :

Thuộc tính mở rộng: chúng tôi đã có một thực tập sinh (xuất sắc!) Thực hiện chúng trong mùa hè như một thử nghiệm, cùng với các loại thành viên khuyến nông khác. Chúng tôi vẫn quan tâm đến điều này, nhưng đó là một thay đổi lớn và chúng tôi cần cảm thấy tự tin rằng nó xứng đáng.

Có vẻ như các thuộc tính mở rộng và các thành viên khác, vẫn là những ứng cử viên tốt sẽ được đưa vào phiên bản tương lai của Roslyn, nhưng có thể không phải là phiên bản 7.0.

Cập nhật: tháng 5 năm 2017

Các thành viên tiện ích mở rộng đã bị đóng vì trùng lặp với mọi vấn đề về tiện ích mở rộng . Các cuộc thảo luận chính trên thực tế là về khả năng mở rộng Loại theo nghĩa rộng. Tính năng này hiện được theo dõi ở đây dưới dạng một đề xuất và đã bị xóa khỏi cột mốc 7.0 .

Cập nhật: Tháng 8 năm 2017 - Tính năng đề xuất C # 8.0

Mặc dù nó vẫn chỉ là một tính năng được đề xuất , nhưng bây giờ chúng ta đã có một cái nhìn rõ ràng hơn về cú pháp của nó. Hãy nhớ rằng đây cũng sẽ là cú pháp mới cho các phương thức mở rộng:

public interface IEmployee 
{
    public decimal Salary { get; set; }
}

public class Employee
{
    public decimal Salary { get; set; }
}

public extension MyPersonExtension extends Person : IEmployee
{
    private static readonly ConditionalWeakTable<Person, Employee> _employees = 
        new ConditionalWeakTable<Person, Employee>();


    public decimal Salary
    {
        get 
        {
            // `this` is the instance of Person
            return _employees.GetOrCreate(this).Salary; 
        }
        set 
        {
            Employee employee = null;
            if (!_employees.TryGetValue(this, out employee)
            {
                employee = _employees.GetOrCreate(this);
            }
            employee.Salary = value;
        }
    }
}

IEmployee person = new Person();
var salary = person.Salary;

Tương tự như các lớp một phần, nhưng được biên dịch thành một lớp / loại riêng biệt trong một cụm khác nhau. Lưu ý bạn cũng sẽ có thể thêm các thành viên tĩnh và toán tử theo cách này. Như đã đề cập trong podcast Mads Torgensen , tiện ích mở rộng sẽ không có bất kỳ trạng thái nào (vì vậy nó không thể thêm các thành viên cá thể riêng tư vào lớp), điều đó có nghĩa là bạn sẽ không thể thêm dữ liệu cá thể được liên kết với cá thể . Lý do được viện dẫn là vì nó có nghĩa là quản lý từ điển nội bộ và nó có thể khó khăn (quản lý bộ nhớ, v.v ...). Đối với điều này, bạn vẫn có thể sử dụng TypeDescriptor/ ConditionalWeakTablekỹ thuật được mô tả trước đó và với phần mở rộng thuộc tính, ẩn nó dưới một thuộc tính đẹp.

Cú pháp vẫn có thể thay đổi vì ngụ ý vấn đề này . Ví dụ, extendscó thể được thay thế bằng formột số có thể cảm thấy tự nhiên hơn và ít liên quan đến java hơn.

Cập nhật tháng 12 năm 2018 - Vai trò, Tiện ích mở rộng và thành viên giao diện tĩnh

Gia hạn mọi thứ đã không đạt được C # 8.0, vì một số hạn chế được giải thích là sự kết thúc của vé GitHub này . Vì vậy, đã có một cuộc thăm dò để cải thiện thiết kế. Ở đây , Mads Torgensen giải thích vai trò và phần mở rộng là gì và chúng khác nhau như thế nào:

Vai trò cho phép các giao diện được thực hiện trên các giá trị cụ thể của một loại nhất định. Các tiện ích mở rộng cho phép các giao diện được triển khai trên tất cả các giá trị của một loại nhất định, trong một vùng mã cụ thể.

Nó có thể được nhìn thấy ở một phần của đề xuất trước đó trong hai trường hợp sử dụng. Các cú pháp mới cho phần mở rộng sẽ là như thế này:

public extension ULongEnumerable of ulong
{
    public IEnumerator<byte> GetEnumerator()
    {
        for (int i = sizeof(ulong); i > 0; i--)
        {
            yield return unchecked((byte)(this >> (i-1)*8));
        }
    }
}

sau đó bạn sẽ có thể làm điều này:

foreach (byte b in 0x_3A_9E_F1_C5_DA_F7_30_16ul)
{
    WriteLine($"{e.Current:X}");
}

Và đối với giao diện tĩnh :

public interface IMonoid<T> where T : IMonoid<T>
{
    static T operator +(T t1, T t2);
    static T Zero { get; }
}

Thêm một thuộc tính mở rộng trên intvà xử lý intnhư IMonoid<int>:

public extension IntMonoid of int : IMonoid<int>
{
    public static int Zero => 0;
}

58
Đây là một trong những câu trả lời hữu ích nhất tôi từng theo dõi trên StackExchange. Cập nhật liên tục với trạng thái và thông báo cho tất cả mọi người quay lại vấn đề này, cung cấp các liên kết vững chắc cho cuộc thảo luận và lịch sử.
bdrelling

25
Thật tuyệt vời khi bạn luôn cập nhật thông tin này - cảm ơn bạn
David Thielen

1
Thật không may, theo nhận xét này, vai trò, tiện ích mở rộng và thành viên giao diện tĩnh chỉ được gắn cờ cho C # 11 :(
Ian Kemp

436

Không, chúng không tồn tại trong C # 3.0 và sẽ không được thêm vào 4.0. Nó nằm trong danh sách các tính năng muốn cho C # vì vậy nó có thể được thêm vào một ngày trong tương lai.

Tại thời điểm này, cách tốt nhất bạn có thể làm là các phương thức mở rộng kiểu GetXXX.


3
Tương tự với các thuộc tính chung: bạn phải sử dụng cú pháp 'GetXXX <>'.
Jay Bazuzi

3
ok, đó là những gì tôi nghĩ. @Jay, yeah, tôi cũng ghét điều đó, hehe. Đặc biệt là không có khả năng để có một người lập chỉ mục chung chung ... thở dài
Svish

75
Liên kết đến danh sách các tính năng muốn?
Dan Esparza

2
Còn phiên bản 6.0 và 7.0 thì sao?
Falk

2
Bất kỳ cập nhật về điều này vào năm 2020?
Chad

265

Không, chúng không tồn tại.

Tôi biết rằng nhóm C # đang xem xét họ tại một thời điểm (hoặc ít nhất là Eric Lippert) - cùng với các nhà xây dựng và vận hành mở rộng (những người này có thể mất một lúc để quay đầu lại, nhưng thật tuyệt ...) Tuy nhiên, tôi đã trốn tránh Tôi đã không thấy bất kỳ bằng chứng nào cho thấy họ sẽ là một phần của C # 4.


EDIT: Chúng không xuất hiện trong C # 5 và kể từ tháng 7 năm 2014, có vẻ như nó sẽ không xuất hiện trong C # 6.

Eric Lippert , Nhà phát triển chính trong nhóm trình biên dịch C # tại Microsoft đến tháng 11 năm 2012, đã viết blog về điều này vào tháng 10 năm 2009:


2
Có, và họ vẫn có thể ẩn trường - đặt một thuộc tính có thể đặt hai thuộc tính bên dưới hoặc ngược lại. (Tôi tưởng tượng một cái gì đó có thuộc tính Kích thước bình thường và thuộc tính mở rộng Chiều rộng / Chiều cao hoặc ngược lại.)
Jon Skeet

23
Bạn không thể liên kết với các phương thức mở rộng ... việc có thể thêm các thuộc tính của riêng bạn cho cơ sở dữ liệu có thể hữu ích trong nhiều tình huống.
Nick

3
@leppie - Giá trị của phần mở rộng thuộc tính sẽ có lợi cho các thuộc tính bool và chuỗi nhiều nhất tôi nghĩ. Bắt thoát khỏi ()ở cuối là nhiều hơn có thể đọc được. Cá nhân tôi biết, ít nhất 90% phần mở rộng tôi viết là của 2 loại đó.
Mã Maverick

4
Để đưa ra một ví dụ về lý do tại sao điều này sẽ hữu ích, tôi có một mô hình EFCF. Trong một số lớp tôi có các thuộc tính chỉ đọc mà tôi sử dụng để trả về thông tin được định dạng: FullName= FirstName + LastName, ShortName= FirstName + LastName[0]. Tôi muốn thêm nhiều thuộc tính này, nhưng tôi không muốn "làm bẩn" các lớp thực tế. Trong trường hợp này, một thuộc tính mở rộng, chỉ đọc, là hoàn hảo vì tôi có thể thêm chức năng, giữ cho lớp chính sạch và vẫn hiển thị thông tin tôi muốn hiển thị trong UI.
Gup3rSuR4c

4
@JonSkeet: Bạn nói đúng, cuối cùng tôi đã làm những gì tôi muốn bằng cách tạo lớp riêng của mình, sau đó gói tất cả các phương thức và thuộc tính của lớp niêm phong có liên quan, sau đó cung cấp static implicit operator FileInfo(FileInfoEx fex)trả về đối tượng FileInfo chứa trong tôi. Điều này thực sự cho phép tôi đối xử với FileInfoEx như thể nó kế thừa từ FileInfo, mặc dù lớp đó đã được niêm phong.
Steve L

27

Cập nhật (cảm ơn @chaost vì đã chỉ ra bản cập nhật này):

Mads Torgersen: "Mở rộng tất cả mọi thứ đã không làm cho nó vào C # 8.0 Nó đã.‘Bắt kịp’, nếu bạn sẽ, trong một cuộc tranh luận rất thú vị về tương lai xa hơn của ngôn ngữ, và bây giờ chúng tôi muốn chắc chắn chúng ta không thêm nó theo cách ức chế những khả năng trong tương lai. Đôi khi thiết kế ngôn ngữ là một trò chơi rất dài! "

Nguồn: phần bình luận trong https://bloss.msdn.microsoft.com/dotnet/2018/11/12/building-c-8-0/


Tôi đã ngừng đếm bao nhiêu lần trong những năm tôi mở câu hỏi này với hy vọng sẽ thấy điều này được thực hiện.

Vâng, cuối cùng tất cả chúng ta có thể vui mừng! Microsoft sẽ giới thiệu điều này trong phiên bản C # 8 sắp tới của họ.

Vì vậy, thay vì làm điều này ...

public static class IntExtensions
{
   public static bool Even(this int value)
   {
        return value % 2 == 0;
   }
}

Cuối cùng chúng ta sẽ có thể làm điều đó như vậy ...

public extension IntExtension extends int
{
    public bool Even => this % 2 == 0;
}

Nguồn: https://blog.ndepend.com/c-8-0-features-glimpse-future/


3
Tuần này các tính năng C # 8.0 đã được công bố và tôi không thấy bất kỳ điều gì đáng tiếc.
Mateo Torres-Ruiz

1
@ MateoTorres-Ruiz Một nhận xét từ 'Mads Torgersen' (C # dev), trả lời ai đó hỏi về nó (3 ngày trước): "Gia hạn mọi thứ đã không được đưa vào C # 8.0. , trong một cuộc tranh luận rất thú vị về tương lai xa hơn của ngôn ngữ, và bây giờ chúng tôi muốn đảm bảo rằng chúng tôi không thêm nó theo cách ngăn cản những khả năng trong tương lai đó. Đôi khi thiết kế ngôn ngữ là một trò chơi rất dài! " Cảm thấy tồi tệ .. (Đọc này trên liên kết Korayems, trong phần bình luận)
Chaost 16/11/18

8

Như @Psyonity đã đề cập, bạn có thể sử dụng điều kiệnWeakTable để thêm thuộc tính vào các đối tượng hiện có. Kết hợp với ExpandoObject động, bạn có thể triển khai các thuộc tính mở rộng động trong một vài dòng:

using System.Dynamic;
using System.Runtime.CompilerServices;

namespace ExtensionProperties
{
    /// <summary>
    /// Dynamically associates properies to a random object instance
    /// </summary>
    /// <example>
    /// var jan = new Person("Jan");
    ///
    /// jan.Age = 24; // regular property of the person object;
    /// jan.DynamicProperties().NumberOfDrinkingBuddies = 27; // not originally scoped to the person object;
    ///
    /// if (jan.Age &lt; jan.DynamicProperties().NumberOfDrinkingBuddies)
    /// Console.WriteLine("Jan drinks too much");
    /// </example>
    /// <remarks>
    /// If you get 'Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create' you should reference Microsoft.CSharp
    /// </remarks>
    public static class ObjectExtensions
    {
        ///<summary>Stores extended data for objects</summary>
        private static ConditionalWeakTable<object, object> extendedData = new ConditionalWeakTable<object, object>();

        /// <summary>
        /// Gets a dynamic collection of properties associated with an object instance,
        /// with a lifetime scoped to the lifetime of the object
        /// </summary>
        /// <param name="obj">The object the properties are associated with</param>
        /// <returns>A dynamic collection of properties associated with an object instance.</returns>
        public static dynamic DynamicProperties(this object obj) => extendedData.GetValue(obj, _ => new ExpandoObject());
    }
}

Một ví dụ sử dụng là trong các bình luận xml:

var jan = new Person("Jan");

jan.Age = 24; // regular property of the person object;
jan.DynamicProperties().NumberOfDrinkingBuddies = 27; // not originally scoped to the person object;

if (jan.Age < jan.DynamicProperties().NumberOfDrinkingBuddies)
{
    Console.WriteLine("Jan drinks too much");
}

jan = null; // NumberOfDrinkingBuddies will also be erased during garbage collection

Câu trả lời hay nhất
N73k

1

Bởi vì gần đây tôi cần điều này, tôi đã xem xét nguồn gốc của câu trả lời trong:

c # mở rộng lớp bằng cách thêm thuộc tính

và tạo ra một phiên bản năng động hơn:

public static class ObjectExtenders
{
    static readonly ConditionalWeakTable<object, List<stringObject>> Flags = new ConditionalWeakTable<object, List<stringObject>>();

    public static string GetFlags(this object objectItem, string key)
    {
        return Flags.GetOrCreateValue(objectItem).Single(x => x.Key == key).Value;
    }

    public static void SetFlags(this object objectItem, string key, string value)
    {
        if (Flags.GetOrCreateValue(objectItem).Any(x => x.Key == key))
        {
            Flags.GetOrCreateValue(objectItem).Single(x => x.Key == key).Value = value;
        }
        else
        {
            Flags.GetOrCreateValue(objectItem).Add(new stringObject()
            {
                Key = key,
                Value = value
            });
        }
    }

    class stringObject
    {
        public string Key;
        public string Value;
    }
}

Nó có lẽ có thể được cải thiện rất nhiều (đặt tên, năng động thay vì chuỗi), tôi hiện đang sử dụng này trong CF 3.5 cùng với một ConditionalWeakTable hacky ( https://gist.github.com/Jan-WillemdeBruyn/db79dd6fdef7b9845e217958db98c4d4 )


Xin lỗi, nhưng mặc dù điều này có vẻ rất kỹ lưỡng, nó không liên quan gì đến các thuộc tính mở rộng, mà chỉ hiển thị các phương thức mở rộng.
Viking
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.