ReSharper เตือน:“ สนามคงที่ในประเภททั่วไป”


261
public class EnumRouteConstraint<T> : IRouteConstraint
    where T : struct
{
    private static readonly Lazy<HashSet<string>> _enumNames; // <--

    static EnumRouteConstraint()
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException(
                Resources.Error.EnumRouteConstraint.FormatWith(typeof(T).FullName));
        }

        string[] names = Enum.GetNames(typeof(T));
        _enumNames = new Lazy<HashSet<string>>(() => new HashSet<string>
        (
            names.Select(name => name), StringComparer.InvariantCultureIgnoreCase
        ));
    }

    public bool Match(HttpContextBase httpContext, Route route, 
                        string parameterName, RouteValueDictionary values, 
                        RouteDirection routeDirection)
    {
        bool match = _enumNames.Value.Contains(values[parameterName].ToString());
        return match;
    }
}

มันผิดหรือเปล่า? ฉันจะสมมติว่านี่มีstatic readonlyเขตข้อมูลสำหรับแต่ละอย่างที่เป็นไปได้EnumRouteConstraint<T>ที่ฉันเกิดขึ้นกับอินสแตนซ์


บางครั้งมันเป็นคุณสมบัติบางครั้งก็น่ารำคาญ ฉันต้องการให้ C # มีคำหลักบางคำที่จะแยกแยะพวกเขา
nawfal

3
นอกจากนี้ยังดูเป็นแบบคงที่สมาชิกของ
-a

คำตอบ:


468

เป็นเรื่องปกติที่จะมีเขตข้อมูลคงที่ในประเภททั่วไปตราบใดที่คุณรู้ว่าคุณจะได้รับหนึ่งเขตข้อมูลต่อชุดค่าผสมประเภทอาร์กิวเมนต์ ฉันเดาว่า R # จะเตือนคุณในกรณีที่คุณไม่ทราบ

นี่คือตัวอย่างของที่:

using System;

public class Generic<T>
{
    // Of course we wouldn't normally have public fields, but...
    public static int Foo;
}

public class Test
{
    public static void Main()
    {
        Generic<string>.Foo = 20;
        Generic<object>.Foo = 10;
        Console.WriteLine(Generic<string>.Foo); // 20
    }
}

อย่างที่คุณเห็นGeneric<string>.Fooเป็นเขตข้อมูลที่แตกต่างจากGeneric<object>.Foo- พวกเขาเก็บค่าที่แยกต่างหาก


สิ่งนี้เป็นจริงเช่นกันเมื่อคลาสทั่วไปสืบทอดจากคลาสที่ไม่ใช่แบบทั่วไปที่มีชนิดสแตติก ตัวอย่างเช่นถ้าฉันสร้างclass BaseFooที่มีสมาชิกแบบคงที่แล้วจะได้รับจากclass Foo<T>: BaseFooทุกFoo<T>ชั้นจะใช้ค่าสมาชิกคงที่เดียวกัน
bikeman868

2
ตอบความคิดเห็นของฉันเองที่นี่ แต่ใช่ Foo <T> ทั้งหมดจะมีค่าคงที่ที่เหมือนกันหากมีอยู่ในคลาสพื้นฐานที่ไม่ใช่แบบทั่วไป ดูdotnetfiddle.net/Wz75ya
bikeman868

147

จากวิกิ JetBrains :

ในกรณีส่วนใหญ่การมีฟิลด์สแตติกในประเภททั่วไปเป็นสัญญาณของข้อผิดพลาด เหตุผลสำหรับสิ่งนี้คือฟิลด์สแตติกในประเภททั่วไปจะไม่ถูกใช้ร่วมกันระหว่างอินสแตนซ์ของประเภทที่สร้างปิดที่แตกต่าง ซึ่งหมายความว่าสำหรับคลาสทั่วไปC<T>ที่มีฟิลด์แบบสแตติกXค่าของC<int>.XและC<string>.X มีค่าแตกต่างกันโดยสิ้นเชิง

ในกรณีที่หายากเมื่อคุณไม่จำเป็นต้อง 'ผู้เชี่ยวชาญ' ทุ่งคงรู้สึกอิสระที่จะปราบปรามการเตือน

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


13
เมื่อใช้งานประเภททั่วไปคุณจะต้องจบด้วยชั้นเรียนที่แตกต่างและแตกต่างกันสำหรับประเภททั่วไปแต่ละประเภทที่คุณโฮสต์ เมื่อประกาศสองคลาสที่ไม่มีการแบ่งแยกคุณจะไม่คาดหวังที่จะแชร์ตัวแปรสแตติกระหว่างคลาสดังนั้นทำไม generics จึงแตกต่างกัน? วิธีเดียวที่จะถือว่าเป็นสิ่งที่หายากคือถ้านักพัฒนาส่วนใหญ่ไม่เข้าใจสิ่งที่พวกเขากำลังทำเมื่อสร้างคลาสทั่วไป
Syndog

2
@Syndog พฤติกรรมที่อธิบายไว้ของสถิตยศาสตร์ในชั้นเรียนทั่วไปดูดีและเข้าใจฉัน แต่ฉันเดาว่าเหตุผลที่อยู่เบื้องหลังคำเตือนเหล่านั้นคือไม่ใช่ทุกทีมที่มีประสบการณ์และมุ่งเน้นการพัฒนาเท่านั้น รหัสที่ถูกต้องจะเกิดข้อผิดพลาดเนื่องจากคุณสมบัติของนักพัฒนา
Stas Ivanov

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

@TomLint ถ้าคุณรู้ว่าคุณกำลังทำอะไรอยู่การระงับคำเตือนย่อมเป็นสิ่งที่ต้องทำ
AakashM

65

นี่ไม่จำเป็นต้องเป็นข้อผิดพลาด - เป็นการเตือนคุณเกี่ยวกับความเข้าใจผิดที่อาจเกิดขึ้น กับ C # generics

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

จากมุมมองMyClassRecipe<T>นี้ไม่ใช่คลาส - เป็นสูตรพิมพ์เขียวว่าคลาสของคุณจะเป็นอย่างไร เมื่อคุณแทนที่ T ด้วยสิ่งที่เป็นรูปธรรมพูดอินสตริง ฯลฯ คุณจะได้เรียน มันถูกกฎหมายอย่างสมบูรณ์แบบที่จะมีสมาชิกแบบสแตติก (ฟิลด์คุณสมบัติวิธีการ) ประกาศในคลาสที่คุณสร้างขึ้นใหม่ มันจะค่อนข้างน่าสงสัยตั้งแต่แรกเห็นถ้าคุณประกาศstatic MyStaticProperty<T> Property { get; set; }ในพิมพ์เขียวในชั้นเรียนของคุณ แต่มันก็ถูกกฎหมายด้วย คุณสมบัติของคุณจะได้รับการกำหนดพารามิเตอร์หรือเทมเพลตเช่นกัน

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


1
ฉันคิดว่าชื่อ C ++ ทำให้ชัดเจนที่สุด ใน C ++ พวกเขาเรียกว่า Templates ซึ่งเป็นสิ่งที่พวกเขาเป็นแม่แบบสำหรับชั้นเรียนที่เป็นรูปธรรม
Michael Brown

8

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

ฉันคิดว่าฉันจะเพิ่มตัวอย่างว่าคุณลักษณะนี้มีประโยชน์อย่างไรเช่นกรณีที่การระงับ R # - การรับรู้ทำให้เข้าใจได้

ลองนึกภาพคุณมีชุดของคลาสเอนทิตีที่คุณต้องการทำให้เป็นอนุกรมพูดกับ Xml คุณสามารถสร้าง serializer สำหรับการใช้นี้new XmlSerializerFactory().CreateSerializer(typeof(SomeClass))แต่แล้วคุณจะต้องสร้าง serializer แยกต่างหากสำหรับแต่ละประเภท ใช้ generics คุณสามารถแทนที่ด้วยสิ่งต่อไปนี้ซึ่งคุณสามารถวางในคลาสทั่วไปที่เอนทิตีสามารถสืบทอดมาจาก:

new XmlSerializerFactory().CreateSerializer(typeof(T))

เนื่องจากคุณอาจไม่ต้องการสร้าง serializer ใหม่ทุกครั้งที่คุณต้องการทำให้เป็นอินสแตนซ์ของประเภทเฉพาะคุณอาจเพิ่มสิ่งนี้:

public class SerializableEntity<T>
{
    // ReSharper disable once StaticMemberInGenericType
    private static XmlSerializer _typeSpecificSerializer;

    private static XmlSerializer TypeSpecificSerializer
    {
        get
        {
            // Only create an instance the first time. In practice, 
            // that will mean once for each variation of T that is used,
            // as each will cause a new class to be created.
            if ((_typeSpecificSerializer == null))
            {
                _typeSpecificSerializer = 
                    new XmlSerializerFactory().CreateSerializer(typeof(T));
            }

            return _typeSpecificSerializer;
        }
    }

    public virtual string Serialize()
    {
        // .... prepare for serializing...

        // Access _typeSpecificSerializer via the property, 
        // and call the Serialize method, which depends on 
        // the specific type T of "this":
        TypeSpecificSerializer.Serialize(xmlWriter, this);
     }
}

_typeSpecificSerializerถ้าชั้นนี้ไม่ได้ทั่วไปแล้วตัวอย่างของการเรียนในแต่ละจะใช้เหมือนกัน

เพราะมันเป็นเรื่องทั่วไป แต่ชุดของอินสแตนซ์ชนิดเดียวกันสำหรับการTจะมีส่วนร่วมเช่นเดียวของ_typeSpecificSerializer(ซึ่งจะได้รับการสร้างขึ้นสำหรับประเภทที่เฉพาะเจาะจงที่) ในขณะที่กรณีที่มีประเภทที่แตกต่างกันสำหรับการจะใช้อินสแตนซ์ที่แตกต่างกันของT_typeSpecificSerializer

ตัวอย่าง

ให้ทั้งสองคลาสที่ขยายSerializableEntity<T>:

// Note that T is MyFirstEntity
public class MyFirstEntity : SerializableEntity<MyFirstEntity>
{
    public string SomeValue { get; set; }
}

// Note that T is OtherEntity
public class OtherEntity : SerializableEntity<OtherEntity >
{
    public int OtherValue { get; set; }
}

... ลองใช้มัน:

var firstInst = new MyFirstEntity{ SomeValue = "Foo" };
var secondInst = new MyFirstEntity{ SomeValue = "Bar" };

var thirdInst = new OtherEntity { OtherValue = 123 };
var fourthInst = new OtherEntity { OtherValue = 456 };

var xmlData1 = firstInst.Serialize();
var xmlData2 = secondInst.Serialize();
var xmlData3 = thirdInst.Serialize();
var xmlData4 = fourthInst.Serialize();

ในกรณีนี้ภายใต้ประทุน, firstInstและsecondInstจะเป็นกรณีของชั้นเดียวกัน (คือSerializableEntity<MyFirstEntity>) _typeSpecificSerializerและเป็นเช่นพวกเขาจะแบ่งปันตัวอย่างของ

thirdInstและfourthInstเป็นอินสแตนซ์ของคลาสที่แตกต่าง ( SerializableEntity<OtherEntity>) และจะแชร์อินสแตนซ์_typeSpecificSerializerที่แตกต่างจากอีกสองรายการ

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


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