การทำเครื่องหมายฟิลด์เป็นประโยชน์ในการอ่านอย่างเดียวใน C # มีประโยชน์อย่างไร


295

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


8
คำตอบภายนอกที่ดี: dotnetperls.com/readonly
OneWorld

3
น่าสนใจ นี่เป็นสิ่งสำคัญเทียบเท่ากับ C # ของคำถามนี้เกี่ยวกับ Java stackoverflow.com/questions/137868/… การสนทนาที่นี่มีความร้อนน้อยกว่ามากแม้ว่า ... hmm ...
RAY

7
อาจเป็นเรื่องที่น่าสังเกตว่าreadonlyเขตข้อมูลของประเภทโครงสร้างกำหนดโทษประสิทธิภาพเมื่อเปรียบเทียบกับเขตข้อมูลที่ไม่แน่นอนที่ไม่ได้กลายพันธุ์เนื่องจากการเรียกใช้สมาชิกของreadonlyเขตข้อมูลประเภทค่าใด ๆจะทำให้คอมไพเลอร์สร้างสำเนาของเขตข้อมูลและเรียกใช้ สมาชิกในนั้น
supercat

2
เพิ่มเติมเกี่ยวกับการลงโทษประสิทธิภาพ: codeblog.jonskeet.uk/2014/07/16/…
CAD bloke

คำตอบ:


171

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

นอกจากนี้ยังใช้งานถ้าคุณไม่ต้องการคอมไพล์ DLLs ภายนอกอีกครั้งที่อ้างอิงค่าคงที่ (เนื่องจากมันถูกแทนที่ในเวลาคอมไพล์)


193

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

อย่างไรก็ตาม "readonly" นั้นแตกต่างจากซีแมนทิคส์ประเภทอ่านอย่างเดียวอื่น ๆ เนื่องจาก CLR บังคับใช้ในขณะรันไทม์โดย CLR คำหลักแบบอ่านอย่างเดียวจะรวบรวมเป็น. ininly ซึ่งสามารถตรวจสอบได้โดย CLR

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

นี่คือรายการที่ดีเกี่ยวกับหนึ่งในประโยชน์ของการเปลี่ยนแปลงไม่ได้: Threading


6
หากคุณอ่านสิ่งนี้stackoverflow.com/questions/9860595/…อ่านได้เฉพาะสมาชิกเท่านั้นที่สามารถแก้ไขได้และดูเหมือนว่าพฤติกรรมที่ไม่สอดคล้องกันโดย. net
Akash Kava

69

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

ดังนั้นจึงเป็นประโยชน์ในการที่จะช่วยให้คุณเขียนรหัสที่แข็งแกร่งขึ้นอ่านได้มากขึ้น ประโยชน์ที่แท้จริงของสิ่งต่าง ๆ เช่นนี้เกิดขึ้นเมื่อคุณทำงานเป็นทีมหรือบำรุงรักษา การประกาศสิ่งที่readonlyคล้ายกับการวางสัญญาสำหรับการใช้งานของตัวแปรนั้นในรหัส คิดว่ามันเป็นเพิ่มเอกสารในลักษณะเดียวกับคำหลักอื่น ๆ เช่นinternalหรือprivateคุณกำลังจะบอกว่า "ตัวแปรนี้ไม่ควรได้รับการแก้ไขหลังจาก initialisation" และยิ่งไปกว่านั้นคุณกำลังบังคับมัน

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


และ otoh ทำให้ภาษาแตกต่าง อย่างไรก็ตามไม่ปฏิเสธใบแจ้งยอดผลประโยชน์ของคุณ
dkretz

3
ฉันเห็นด้วย แต่ฉันคิดว่าผลประโยชน์ที่แท้จริงเกิดขึ้นเมื่อมีคนมากกว่าหนึ่งคนทำงานกับรหัส มันเหมือนกับมีคำสั่งการออกแบบขนาดเล็กภายในรหัสสัญญาสำหรับการใช้งาน ฉันอาจจะใส่คำตอบนั้นฮิฮิ
Xiaofu

7
คำตอบนี้และการอภิปรายเป็นจริงคำตอบที่ดีที่สุดในความคิดของฉัน 1
เจฟมาร์ติน

@Xiaofu: คุณทำให้ฉันคงที่ในความคิดของการอ่านอย่างเดียวฮ่าฮ่าฮ่าคำอธิบายที่สวยงามที่ไม่มีใครในโลกนี้ที่สามารถอธิบายจิตใจที่งี่เง่าที่สุดเข้าใจ
Learner

นั่นคือคุณตั้งใจที่จะใช้รหัสของคุณว่าค่านี้ไม่ควรเปลี่ยนแปลงได้ตลอดเวลา
Andez

51

ที่จะนำมาใช้ในแง่ปฏิบัติมาก:

หากคุณใช้ const ใน dll A และ dll B อ้างอิงว่า const ค่าของ const นั้นจะถูกรวบรวมเป็น dll B. หากคุณ redeploy dll A ด้วยค่าใหม่สำหรับ const นั้น dll B จะยังคงใช้ค่าเดิม

หากคุณใช้การอ้างอิงแบบอ่านอย่างเดียวใน dll A และ dll B ที่อ่านได้อย่างเดียวการอ่านอย่างเดียวจะถูกค้นหาที่รันไทม์เสมอ ซึ่งหมายความว่าถ้าคุณ redeploy dll A ที่มีค่าใหม่สำหรับ readonly นั้นเท่านั้น DLL B จะใช้ค่าใหม่นั้น


4
นี่เป็นตัวอย่างการปฏิบัติที่ดีที่จะเข้าใจความแตกต่าง ขอบคุณ
Shyju

บนมืออื่น ๆ ที่อาจจะมีกำไรจากผลการดำเนินงานมากกว่าconst readonlyนี่คือคำอธิบายที่ลึกซึ้งยิ่งขึ้นด้วยรหัส: dotnetperls.com/readonly
Dio Phung

2
ฉันคิดว่าคำศัพท์เชิงปฏิบัติที่ใหญ่ที่สุดนั้นหายไปจากคำตอบนี้: ความสามารถในการเก็บค่าที่คำนวณได้ที่รันไทม์ไปยังreadonlyเขตข้อมูล คุณไม่สามารถจัดเก็บnew object();ในconstและที่เหมาะสมเพราะคุณไม่สามารถอบสิ่งที่ไม่คุ้มค่าเช่นการอ้างอิงไปยังแอสเซมบลีอื่น ๆ ในระหว่างการรวบรวมเวลาโดยไม่ต้องเปลี่ยนข้อมูลประจำตัว
binki

14

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

นี้ใช้เฉพาะถ้าฟิลด์อ่านได้อย่างเดียวนอกจากนี้ยังมีการทำเครื่องหมายเป็นแบบคงที่ ในกรณีนั้นคอมไพเลอร์ JIT สามารถสันนิษฐานได้ว่าฟิลด์สแตติกนี้จะไม่เปลี่ยนแปลง คอมไพเลอร์ JIT สามารถพิจารณาสิ่งนี้เมื่อรวบรวมวิธีการของคลาส

ตัวอย่างทั่วไป: คลาสของคุณอาจมีฟิลด์IsDebugLoggingEnabledแบบอ่านอย่างเดียวที่กำหนดค่าเริ่มต้นในตัวสร้าง (เช่นตามไฟล์การกำหนดค่า) เมื่อวิธีที่แท้จริงคือการคอมไพล์ JIT คอมไพเลอร์อาจ ommit ส่วนทั้งหมดของรหัสเมื่อไม่ได้เปิดใช้งานการบันทึกการดีบัก

ฉันไม่ได้ตรวจสอบว่าการเพิ่มประสิทธิภาพนี้มีการใช้งานจริงในคอมไพเลอร์ JIT เวอร์ชันปัจจุบันหรือไม่ดังนั้นนี่เป็นเพียงการเก็งกำไร


มีแหล่งที่มาสำหรับสิ่งนี้หรือไม่?
Sedat Kapanoglu

4
คอมไพเลอร์ JIT ปัจจุบันที่ใช้งานจริงและมีตั้งแต่ CLR 3.5 github.com/dotnet/coreclr/issues/1079
mirhagk

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

9

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


4

อย่าลืมว่ามีวิธีแก้ปัญหาเพื่อให้ได้readonlyฟิลด์ที่อยู่ด้านนอกของตัวสร้างโดยใช้outพารามิเตอร์

ยุ่งเล็กน้อย แต่:

private readonly int _someNumber;
private readonly string _someText;

public MyClass(int someNumber) : this(data, null)
{ }

public MyClass(int someNumber, string someText)
{
    Initialise(out _someNumber, someNumber, out _someText, someText);
}

private void Initialise(out int _someNumber, int someNumber, out string _someText, string someText)
{
    //some logic
}

การอภิปรายเพิ่มเติมที่นี่: http://www.adamjamesnaylor.com/2013/01/23/Setting-Readonly-Fields-From-Chained-Constructors.aspx


4
ฟิลด์ยังคงได้รับมอบหมายใน Constructor .. ไม่มี "การไปไหนมาไหน" ไม่สำคัญว่าค่าจะมาจากนิพจน์เดียวจากประเภทซับซ้อนที่สลายตัวหรือมอบหมายผ่านการโทรโดยการอ้างอิงความหมายของout..
2864740

สิ่งนี้ไม่แม้แต่จะพยายามตอบคำถาม
Sheridan

2

น่าแปลกที่การอ่านอย่างเดียวอาจส่งผลให้เกิดรหัสช้าลงตามที่ Jon Skeet พบเมื่อทำการทดสอบไลบรารี Noda Time ของเขา ในกรณีนี้การทดสอบที่ใช้เวลา 20 วินาทีใช้เวลาเพียง 4 วินาทีหลังจากลบแบบอ่านอย่างเดียว

https://codeblog.jonskeet.uk/2014/07/16/micro-optimization-the-surprising-inefficiency-of-readonly-fields/


3
โปรดทราบว่าถ้าเขตข้อมูลเป็นประเภทที่อยู่readonly structใน C # 7.2 ประโยชน์ของการทำให้เขตข้อมูลไม่หายไปแบบอ่านอย่างเดียว
Jon Skeet

1

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


1

การเพิ่มมุมมองพื้นฐานเพื่อตอบคำถามนี้:

คุณสมบัติสามารถแสดงเป็นแบบอ่านอย่างเดียวโดยออกจากsetโอเปอเรเตอร์ ดังนั้นในกรณีส่วนใหญ่คุณไม่จำเป็นต้องเพิ่มreadonlyคำหลักในคุณสมบัติ:

public int Foo { get; }  // a readonly property

ตรงกันข้ามกับที่: ฟิลด์ต้องการreadonlyคำหลักเพื่อให้บรรลุผลที่คล้ายกัน:

public readonly int Foo; // a readonly field

ดังนั้นข้อดีอย่างหนึ่งของการทำเครื่องหมายฟิลด์ที่readonlyสามารถบรรลุระดับการป้องกันการเขียนที่คล้ายกันเป็นคุณสมบัติที่ไม่มีsetตัวดำเนินการ - โดยไม่ต้องเปลี่ยนฟิลด์เป็นคุณสมบัติถ้าด้วยเหตุผลใดก็ตามที่ต้องการ


มีความแตกต่างในพฤติกรรมระหว่าง 2 หรือไม่?
petrosmm

0

ระมัดระวังกับอาร์เรย์แบบอ่านอย่างเดียว หากสิ่งเหล่านี้ถูกเปิดเผยว่าไคลเอนต์เป็นวัตถุ (คุณอาจทำสิ่งนี้เพื่อ COM interop อย่างที่ฉันทำ) ไคลเอ็นต์สามารถจัดการค่าอาร์เรย์ ใช้เมธอด Clone () เมื่อคืนอาเรย์เป็นวัตถุ


20
ไม่มี เปิดเผย a ReadOnlyCollection<T>แทน array
Slaks

นี่ควรเป็นความเห็นไม่ใช่คำตอบเนื่องจากไม่ได้ให้คำตอบกับคำถาม ...
Peter

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

ตั้งแต่ปี 2013 คุณสามารถใช้ImmutableArray<T>ซึ่งจะหลีกเลี่ยงการชกมวยเพื่ออินเทอร์เฟซ ( IReadOnlyList<T>) หรือล้อมรอบในคลาส ( ReadOnlyCollection) มันมีประสิทธิภาพเทียบเท่ากับอาร์เรย์เนทีฟ: blogs.msdn.microsoft.com/dotnet/2013/06/24/…
Charles Taylor

0

อาจมีประโยชน์ด้านประสิทธิภาพใน WPF เนื่องจากไม่จำเป็นต้องใช้ DependencyProperties ที่มีราคาแพง สิ่งนี้มีประโยชน์อย่างยิ่งกับคอลเล็กชัน


0

อีกส่วนที่น่าสนใจของการใช้การทำเครื่องหมายแบบอ่านอย่างเดียวสามารถป้องกันฟิลด์จากการเริ่มต้นในซิงเกิล

เช่นในรหัสจากcsharpindepth :

public sealed class Singleton
{
    private static readonly Lazy<Singleton> lazy =
        new Lazy<Singleton>(() => new Singleton());

    public static Singleton Instance { get { return lazy.Value; } }

    private Singleton()
    {
    }
}

มีบทบาทเล็ก ๆ ในการปกป้องเขตข้อมูล Singleton แบบอ่านอย่างเดียวจากการเริ่มต้นสองครั้ง รายละเอียดอื่นคือสำหรับสถานการณ์ที่กล่าวถึงคุณไม่สามารถใช้ const ได้เนื่องจากกองกำลัง const สร้างระหว่างเวลารวบรวม แต่ซิงเกิลทำการสร้าง ณ รันไทม์


0

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

รหัส https://repl.it/HvRU/1

using System;

class MainClass {
    public static void Main (string[] args) {

        Console.WriteLine(new Test().c);
        Console.WriteLine(new Test("Constructor").c);
        Console.WriteLine(new Test().ChangeC()); //Error A readonly field 
        // `MainClass.Test.c' cannot be assigned to (except in a constructor or a 
        // variable initializer)
    }


    public class Test {
        public readonly string c = "Hello World";
        public Test() {

        }

        public Test(string val) {
          c = val;
        }

        public string ChangeC() {
            c = "Method";
            return c ;
        }
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.