C # มีคุณสมบัติส่วนขยายหรือไม่


768

C # มีคุณสมบัติส่วนขยายหรือไม่

ตัวอย่างเช่นฉันสามารถเพิ่มคุณสมบัติส่วนขยายให้DateTimeFormatInfoเรียกได้ว่าShortDateLongTimeFormatจะส่งคืนShortDatePattern + " " + LongTimePatternหรือไม่


14
ฉันต้องการเพิ่มวิธีการขยายที่เรียกว่า IsNull บน Nullable <T> ซึ่งเพิ่งจะกลับมา! HasValue .IsNull () สวยน้อยกว่าแน่นอน. IsNull
Ken

1
ฉันพบว่ามีประโยชน์สำหรับผู้ประกอบการ trinary?
PedroC88

2
ฉันต้องการสิ่งนี้เพื่อเลียนแบบ Java enumซึ่งสามารถมีคุณสมบัติและวิธีการ C # enumไม่สามารถมีคุณสมบัติหรือวิธีการ แต่คุณสามารถสร้างวิธีการขยายในพวกเขา คำถามนี้มีประโยชน์สำหรับฉันและไม่ควรปิด
Ian McLaird

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

2
ควรจะทำอย่างใดอย่างหนึ่ง
Rootel

คำตอบ:


365

ในขณะนี้มันยังไม่รองรับโดยผู้รวบรวม Roslyn ...

จนถึงขณะนี้คุณสมบัติส่วนขยายยังไม่เห็นว่ามีค่าเพียงพอที่จะรวมไว้ในมาตรฐาน C # รุ่นก่อนหน้า C # 7และC # 8.0เห็นว่านี่เป็นแชมเปี้ยนข้อเสนอ แต่ยังไม่เปิดตัวส่วนใหญ่เพราะแม้ว่าจะมีการนำไปใช้แล้วพวกเขาต้องการทำให้ถูกต้องตั้งแต่เริ่มต้น

แต่มันจะ ...

มีรายการสมาชิกส่วนขยายในรายการงานC # 7ดังนั้นจึงอาจได้รับการสนับสนุนในอนาคตอันใกล้ สถานะปัจจุบันของสถานที่ให้บริการส่วนต่อขยายสามารถพบได้บนGithub ภายใต้รายการที่เกี่ยวข้อง

อย่างไรก็ตามมีหัวข้อที่มีแนวโน้มมากขึ้นซึ่งก็คือ"ขยายทุกอย่าง"โดยเน้นที่คุณสมบัติโดยเฉพาะอย่างยิ่งและคลาสแบบคงที่หรือฟิลด์

ยิ่งกว่านั้นคุณสามารถใช้วิธีแก้ปัญหา

ตามที่ระบุไว้ในบทความนี้คุณสามารถใช้TypeDescriptorความสามารถในการแนบคุณสมบัติกับวัตถุตัวอย่างที่รันไทม์ อย่างไรก็ตามมันไม่ได้ใช้ไวยากรณ์ของคุณสมบัติมาตรฐาน
มันแตกต่างกันเล็กน้อยจากน้ำตาล syntactic เพียงเล็กน้อยเพิ่มความเป็นไปได้ในการกำหนดคุณสมบัติแบบขยายเช่น
string Data(this MyClass instance)นามแฝงสำหรับวิธีการขยาย
string GetData(this MyClass instance)เนื่องจากเก็บข้อมูลไว้ในชั้นเรียน

ฉันหวังว่า C # 7 จะให้ส่วนขยายที่โดดเด่นเต็มทุกอย่าง (คุณสมบัติและฟิลด์) อย่างไรก็ตามในจุดนั้นจะมีเวลาบอกเท่านั้น

และอย่าลังเลที่จะมีส่วนร่วมเพราะซอฟต์แวร์แห่งวันพรุ่งนี้จะมาจากชุมชน

อัพเดท: สิงหาคม 2559

ในขณะที่ทีม dotnet เผยแพร่สิ่งใหม่ใน C # 7.0และจากความคิดเห็นของMads Torgensen :

คุณสมบัติส่วนขยาย: เรามีการฝึกงาน (ยอดเยี่ยม!) นำพวกมันไปใช้ในช่วงฤดูร้อนพร้อมกับสมาชิกส่วนขยายอื่น ๆ เรายังคงสนใจในสิ่งนี้ แต่มันเป็นการเปลี่ยนแปลงครั้งใหญ่และเราต้องรู้สึกมั่นใจว่ามันคุ้มค่า

ดูเหมือนว่าคุณสมบัติส่วนขยายและสมาชิกอื่น ๆ ยังคงเป็นผู้สมัครที่ดีที่จะรวมอยู่ในการเปิดตัวในอนาคตของ Roslyn แต่อาจไม่ใช่ 7.0

อัปเดต: พฤษภาคม 2017

สมาชิกส่วนขยายถูกปิดเป็นซ้ำกับปัญหาส่วนขยายทุกอย่างที่ปิดเช่นกัน การสนทนาหลักในความเป็นจริงเกี่ยวกับการขยายประเภทในความหมายกว้าง คุณลักษณะนี้จะติดตามตอนนี้ที่นี่เป็นข้อเสนอและได้ถูกลบออกจาก 7.0 ก้าว

อัปเดต: สิงหาคม 2017 - คุณลักษณะที่เสนอ C # 8.0

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

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;

คล้ายกับคลาสบางส่วน แต่ถูกคอมไพล์เป็นคลาส / ชนิดแยกในแอสเซมบลีที่แตกต่างกัน หมายเหตุคุณจะสามารถเพิ่มสมาชิกแบบคงที่และผู้ประกอบการด้วยวิธีนี้ ดังกล่าวในMads Torgensen พอดคาสต์ , ส่วนขยายจะไม่ได้มีรัฐใด ๆ (ดังนั้นจึงไม่สามารถเพิ่มสมาชิก instance ส่วนตัวในชั้นเรียน) ซึ่งหมายความว่าคุณจะไม่สามารถที่จะเพิ่มข้อมูลส่วนตัวเช่นที่เชื่อมโยงกับอินสแตนซ์ เหตุผลที่เรียกเช่นนั้นก็คือมันจะหมายถึงการจัดการพจนานุกรมภายในและอาจเป็นเรื่องยาก (การจัดการหน่วยความจำ ฯลฯ ... ) สำหรับสิ่งนี้คุณยังสามารถใช้TypeDescriptor/ ConditionalWeakTableเทคนิคที่อธิบายไว้ก่อนหน้านี้และด้วยส่วนขยายคุณสมบัติซ่อนไว้ภายใต้คุณสมบัติที่ดี

ไวยากรณ์ยังคงมีการเปลี่ยนแปลงตามนัยปัญหานี้ ตัวอย่างเช่นextendsอาจถูกแทนที่ด้วยforซึ่งบางคนอาจรู้สึกเป็นธรรมชาติมากขึ้นและน้อยกว่าจาวาที่เกี่ยวข้อง

อัปเดตธันวาคม 2561 - บทบาทส่วนขยายและสมาชิกส่วนต่อประสานแบบคงที่

การขยายทุกอย่างไม่ได้ทำให้เป็น C # 8.0 เนื่องจากมีข้อบกพร่องบางอย่างที่อธิบายไว้ในตอนท้ายของตั๋ว GitHubนี้ ดังนั้นจึงมีการสำรวจเพื่อปรับปรุงการออกแบบ ที่นี่ Mads Torgensen อธิบายว่าอะไรคือบทบาทและส่วนขยายและความแตกต่าง:

บทบาทอนุญาตให้อินเตอร์เฟสถูกนำไปใช้กับค่าเฉพาะของชนิดที่กำหนด ส่วนต่อขยายอนุญาตให้มีการใช้งานอินเทอร์เฟซกับค่าทั้งหมดของประเภทที่กำหนดภายในขอบเขตรหัสเฉพาะ

มันสามารถเห็นได้ที่แยกข้อเสนอก่อนหน้าในสองกรณีการใช้งาน ไวยากรณ์ใหม่สำหรับการขยายจะเป็นเช่นนี้

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

จากนั้นคุณจะสามารถทำสิ่งนี้:

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

และสำหรับอินเทอร์เฟซแบบคงที่ :

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

เพิ่มคุณสมบัติขยายบนintและรักษาintเป็นIMonoid<int>:

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

57
นี่เป็นหนึ่งในคำตอบที่มีประโยชน์ที่สุดที่ฉันเคยติดตามใน StackExchange อัปเดตสถานะอย่างต่อเนื่องและแจ้งให้ทุกคนทราบว่ากลับมาที่นี่โดยมีลิงก์ที่เชื่อมโยงไปยังการอภิปรายและประวัติ
bdrelling

25
ที่ยอดเยี่ยมมันที่คุณกำลังรักษาขึ้นนี้ถึงวันที่ - ขอบคุณ
เดวิด Thielen

1
น่าเสียดายที่ความคิดเห็นบทบาทส่วนขยายและสมาชิกส่วนต่อประสานคงที่จะตั้งค่าสถานะเฉพาะสำหรับ C # 11 :(
Ian Kemp

436

ไม่มีพวกเขาไม่มีอยู่ใน C # 3.0 และจะไม่ถูกเพิ่มใน 4.0 อยู่ในรายการคุณลักษณะที่ต้องการสำหรับ C # ดังนั้นจึงอาจมีการเพิ่มในวันที่ในอนาคต

ณ จุดนี้สิ่งที่ดีที่สุดที่คุณสามารถทำได้คือวิธีการขยายสไตล์ของ GetXXX


3
เช่นเดียวกันกับคุณสมบัติทั่วไป: คุณต้องใช้ไวยากรณ์ 'GetXXX <>'
Jay Bazuzi

3
ตกลงนั่นคือสิ่งที่ฉันคิด @Jay ใช่ฉันเกลียดที่เกินไป hehe โดยเฉพาะอย่างยิ่งการไร้ความสามารถที่จะมีดรรชนีทั่วไป ... ถอนหายใจ
Svish

75
เชื่อมโยงไปยังรายการสถานที่ที่ต้องการหรือไม่
Dan Esparza

2
แล้วเวอร์ชั่น 6.0 และ 7.0 ล่ะ?
Falk

2
มีการอัพเดทอะไรบ้างในปี 2020?
ชาด

265

ไม่พวกเขาไม่มีอยู่จริง

ฉันรู้ว่าทีม C # กำลังพิจารณาพวกเขาอยู่ ณ จุดหนึ่ง (หรืออย่างน้อย Eric Lippert) - พร้อมด้วยตัวสร้างส่วนขยายและตัวดำเนินการ (ซึ่งอาจใช้เวลาสักครู่เพื่อให้ได้หัวของคุณ ไม่เห็นหลักฐานว่าพวกเขาจะเป็นส่วนหนึ่งของ C # 4


แก้ไข: พวกเขาไม่ปรากฏใน C # 5 และ ณ เดือนกรกฎาคม 2014 ดูเหมือนว่าจะเป็น C # 6 เหมือนกัน

Eric Lippertผู้พัฒนาหลักของทีมคอมไพเลอร์ C # ที่ Microsoft ถึงพฤศจิกายน 2012 บล็อกเกี่ยวกับเรื่องนี้ในเดือนตุลาคมปี 2009:


2
ใช่และพวกเขายังสามารถซ่อนฟิลด์ได้ - การตั้งค่าคุณสมบัติเดียวอาจตั้งค่าคุณสมบัติสองรายการไว้ข้างใต้หรือกลับกัน (ลองนึกภาพบางอย่างที่มีคุณสมบัติขนาดปกติและคุณสมบัติส่วนขยายความกว้าง / ความสูงหรือในทางกลับกัน) พวกเขาต้องการใช้ประโยชน์มากกว่าสิ่งที่เป็นแบบอ่านอย่างเดียว แต่ฉันสงสัยว่า
Jon Skeet

23
คุณไม่สามารถผูกกับวิธีการขยาย ... ความสามารถในการเพิ่มคุณสมบัติของคุณเองสำหรับฐานข้อมูลอาจเป็นประโยชน์ในหลาย ๆ
Nick

3
@leppie - มูลค่าของส่วนขยายคุณสมบัติจะเป็นประโยชน์ต่อคุณสมบัติบูลและสตริงมากที่สุดที่ฉันคิด การกำจัดของ()ที่สิ้นสุดเป็นมากเพิ่มเติมสามารถอ่านได้ ฉันรู้จักฉันเป็นการส่วนตัวอย่างน้อย 90% ของส่วนขยายที่ฉันเขียนนั้นเป็น 2 ประเภทนั้น
รหัสไม่ฝักใฝ่ฝ่ายใด

4
เพื่อยกตัวอย่างว่าทำไมสิ่งนี้ถึงมีประโยชน์ฉันมีโมเดล EFCF ในบางส่วนของชั้นเรียนที่ผมมีคุณสมบัติในการอ่านอย่างเดียวที่ผมใช้ในการส่งกลับข้อมูลการจัดรูปแบบ: FullName= FirstName + LastName, =ShortName FirstName + LastName[0]ฉันต้องการที่จะเพิ่มคุณสมบัติเหล่านี้มากขึ้น แต่ฉันไม่ต้องการที่จะ "สกปรก" ชั้นเรียนจริง ในกรณีนี้คุณสมบัติส่วนขยายที่เป็นแบบอ่านอย่างเดียวนั้นสมบูรณ์แบบเพราะฉันสามารถเพิ่มฟังก์ชันการทำงานรักษาคลาสหลักให้สะอาดและยังคงเปิดเผยข้อมูลที่ต้องการเปิดเผยใน UI
Gup3rSuR4c

4
@ JonSkeet: ถูกต้องฉันลงเอยทำสิ่งที่ฉันต้องการด้วยการสร้างคลาสของตัวเองแล้วห่อเมธอดและคุณสมบัติของคลาสที่ปิดผนึกที่เกี่ยวข้องทั้งหมดจากนั้นให้static implicit operator FileInfo(FileInfoEx fex)สิ่งที่ส่งคืนวัตถุ FileInfo ที่มีอยู่ของฉัน สิ่งนี้ช่วยให้ฉันจัดการกับ FileInfoEx ได้อย่างมีประสิทธิภาพราวกับว่าสืบทอดมาจาก FileInfo แม้ว่าคลาสนั้นจะถูกปิดผนึกก็ตาม
Steve L

27

อัปเดต (ขอบคุณ@chaostสำหรับการชี้การอัปเดตนี้):

Mads Torgersen: "การขยายทุกอย่างไม่ได้ทำให้เป็น C # 8.0 มัน" ทัน "หากคุณต้องการในการอภิปรายที่น่าตื่นเต้นเกี่ยวกับอนาคตของภาษาและตอนนี้เราต้องการให้แน่ใจว่าเราไม่ได้ เพิ่มในทางที่ขัดขวางความเป็นไปได้ในอนาคตบางครั้งการออกแบบภาษาเป็นเกมที่ยาวมาก! "

ที่มา: ส่วนความคิดเห็นในhttps://blogs.msdn.microsoft.com/dotnet/2018/11/12/building-c-8-0/


ฉันหยุดนับกี่ครั้งในช่วงหลายปีที่ฉันเปิดคำถามนี้ด้วยความหวังว่าจะได้เห็นการดำเนินการนี้

ในที่สุดเราก็สามารถชื่นชมยินดีได้! Microsoft กำลังจะแนะนำสิ่งนี้ในการเปิดตัว C # 8 ที่กำลังจะมาถึง

ดังนั้นแทนที่จะทำสิ่งนี้ ...

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

ในที่สุดเราก็สามารถทำเช่นนั้นได้ ...

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

ที่มา: https://blog.ndepend.com/c-8-0-features-glimpse-future/


3
มีการประกาศฟีเจอร์ C # 8.0 ในสัปดาห์นี้และฉันก็ไม่เห็นว่าน่าเสียดาย
Mateo Torres-Ruiz

1
@ MateoTorres-Ruiz ความคิดเห็นจาก 'Mads Torgersen' (C # dev) ตอบกลับคนที่ถามเกี่ยวกับเรื่องนี้ (3 วันที่ผ่านมา): "การขยายทุกอย่างไม่ได้ทำให้เป็น C # 8.0 มันจะ" ติด "ถ้าคุณจะ ในการถกเถียงที่น่าตื่นเต้นอย่างมากเกี่ยวกับอนาคตของภาษาและตอนนี้เราต้องการให้แน่ใจว่าเราจะไม่เพิ่มมันเข้าไปในทางที่จะยับยั้งโอกาสในอนาคตเหล่านั้นบางครั้งการออกแบบภาษาเป็นเกมที่ยาวมาก! " รู้สึกไม่ดี .. (อ่านนี้ในการเชื่อมโยง Korayems ในส่วนความคิดเห็น)
Chaost

8

ตามที่ @Psyonity ที่กล่าวถึงคุณสามารถใช้ conditionalWeakTable เพื่อเพิ่มคุณสมบัติให้กับวัตถุที่มีอยู่ รวมกับ ExpandoObject แบบไดนามิกคุณสามารถใช้คุณสมบัติส่วนขยายแบบไดนามิกในไม่กี่บรรทัด:

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

ตัวอย่างการใช้งานอยู่ในความคิดเห็น 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

คำตอบที่ดีที่สุด
N73k

1

เพราะเมื่อเร็ว ๆ นี้ฉันต้องการสิ่งนี้ฉันจึงดูที่มาของคำตอบใน:

c # ขยายคลาสโดยการเพิ่มคุณสมบัติ

และสร้างเวอร์ชันที่มีพลวัตมากขึ้น:

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

มันอาจจะดีขึ้นมาก (การตั้งชื่อแบบไดนามิกแทนสตริง) ฉันใช้สิ่งนี้ใน CF 3.5 ร่วมกับ ConditionalWeakTable ที่แฮ็ค ( https://gist.github.com/Jan-WillemdeBruyn/db79dd6fdef7b9845e217958db98c4d4 )


ขออภัยแม้ว่าจะดูละเอียดมาก แต่ก็ไม่มีอะไรเกี่ยวข้องกับคุณสมบัติส่วนขยาย แต่แสดงวิธีการขยายเท่านั้น
ไวกิ้ง
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.