ReSharper cảnh báo: Trường tĩnh trong kiểu chung


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

Điều này có sai không? Tôi sẽ giả định rằng điều này thực sự có một static readonlytrường cho mỗi khả năng EnumRouteConstraint<T>mà tôi có thể xảy ra.


Đôi khi nó là một tính năng, đôi khi là một phiền toái. Tôi muốn C # có một số từ khóa để phân biệt chúng
nawfal

Câu trả lời:


468

Thật tốt khi có một trường tĩnh trong một loại chung, miễn là bạn biết rằng bạn thực sự sẽ nhận được một trường cho mỗi kết hợp của các đối số loại. Tôi đoán là R # chỉ cảnh báo bạn trong trường hợp bạn không biết điều đó.

Đây là một ví dụ về điều đó:

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

Như bạn có thể thấy, Generic<string>.Foolà một trường khác với Generic<object>.Foo- chúng giữ các giá trị riêng biệt.


Điều này cũng đúng khi các lớp chung kế thừa từ một lớp không chung có chứa các kiểu tĩnh. Ví dụ, nếu tôi tạo class BaseFoocó chứa một thành viên tĩnh, thì xuất phát từ nó class Foo<T>: BaseFootất cả Foo<T>các lớp sẽ chia sẻ cùng một giá trị thành viên tĩnh?
bikeman868

2
Trả lời nhận xét của riêng tôi ở đây, nhưng có, tất cả Foo <T> sẽ có cùng giá trị tĩnh nếu nó được chứa trong một lớp cơ sở không chung chung. Xem dotnetfiddle.net/Wz75ya
bikeman868

147

Từ wiki JetBrains :

Trong phần lớn các trường hợp, có trường tĩnh trong loại chung là dấu hiệu của lỗi. Lý do cho điều này là một trường tĩnh trong một loại chung sẽ không được chia sẻ giữa các trường hợp của các loại được xây dựng gần khác nhau. Điều này có nghĩa là đối với một lớp chung C<T>có trường tĩnh X, các giá trị C<int>.XC<string>.X có các giá trị độc lập, hoàn toàn khác nhau.

Trong những trường hợp hiếm hoi khi bạn làm cần các lĩnh vực tĩnh 'chuyên', cảm thấy tự do để ngăn chặn cảnh báo.

Nếu bạn cần có một trường tĩnh được chia sẻ giữa các phiên bản với các đối số chung khác nhau, hãy xác định một lớp cơ sở không chung chung để lưu trữ các thành viên tĩnh của bạn, sau đó đặt loại chung của bạn thành kế thừa từ loại này.


13
Khi sử dụng một loại chung, về mặt kỹ thuật, bạn kết thúc với một lớp riêng biệt và riêng biệt cho từng loại chung mà bạn đang lưu trữ. Khi khai báo hai lớp riêng biệt, không chung chung, bạn sẽ không muốn chia sẻ các biến tĩnh giữa chúng, vậy tại sao các tổng quát phải khác nhau? Cách duy nhất có thể được coi là hiếm này là nếu phần lớn các nhà phát triển không hiểu họ đang làm gì khi tạo các lớp chung.
Syndog

2
@Syndog hành vi được mô tả của statics trong một lớp chung có vẻ tốt và dễ hiểu đối với tôi. Nhưng tôi đoán lý do đằng sau những cảnh báo đó là không phải mọi đội ngũ chỉ có các nhà phát triển có kinh nghiệm và tập trung. Mã chính xác trở nên dễ bị lỗi do trình độ của nhà phát triển.
Stas Ivanov

Nhưng điều gì sẽ xảy ra nếu tôi không muốn tạo một lớp cơ sở không chung chung chỉ để giữ các trường tĩnh này. Tôi chỉ có thể đàn áp các cảnh báo, trong trường hợp này?
Tom Lint

@TomLint nếu bạn biết bạn đang làm gì thì việc ngăn chặn các cảnh báo thực sự là việc cần làm.
AakashM

65

Đây không hẳn là một lỗi - nó đang cảnh báo bạn về một sự hiểu lầm tiềm ẩn về thuốc generic C #.

Cách dễ nhất để nhớ những gì generic tạo ra là như sau: Generics là "bản thiết kế" để tạo các lớp, giống như các lớp là "bản thiết kế" để tạo đối tượng. (Chà, đây là một sự đơn giản hóa. Bạn cũng có thể sử dụng chung phương thức.)

Từ quan điểm MyClassRecipe<T>này không phải là một lớp - nó là một công thức, một kế hoạch chi tiết, về những gì lớp của bạn sẽ trông như thế nào. Khi bạn thay thế T bằng một cái gì đó cụ thể, hãy nói int, chuỗi, v.v., bạn sẽ có một lớp. Hoàn toàn hợp pháp khi có một thành viên tĩnh (trường, thuộc tính, phương thức) được khai báo trong lớp mới tạo của bạn (như trong bất kỳ lớp nào khác) và không có dấu hiệu của bất kỳ lỗi nào ở đây. Nó sẽ hơi đáng ngờ, ngay từ cái nhìn đầu tiên, nếu bạn tuyên bố static MyStaticProperty<T> Property { get; set; }trong kế hoạch chi tiết của lớp mình, nhưng điều này cũng hợp pháp. Tài sản của bạn cũng sẽ được tham số hóa, hoặc templated.

Không có thắc mắc trong thống kê VB được gọi shared. Tuy nhiên, trong trường hợp này, bạn nên lưu ý rằng các thành viên "chia sẻ" như vậy chỉ được chia sẻ giữa các trường hợp của cùng một lớp chính xác, và không nằm trong số các lớp riêng biệt được tạo ra bằng cách thay thế <T>bằng một thứ khác.


1
Tôi nghĩ rằng tên C ++ làm cho nó rõ ràng nhất trong tất cả. Trong C ++, chúng được gọi là Mẫu, đó là những gì chúng là, Mẫu cho các lớp cụ thể.
Michael Brown

8

Có một số câu trả lời tốt ở đây đã giải thích cảnh báo và lý do cho nó. Một số trong những trạng thái này giống như có một trường tĩnh trong một loại chung thường là một lỗi .

Tôi nghĩ rằng tôi đã thêm một ví dụ về cách tính năng này có thể hữu ích, ví dụ như trường hợp triệt tiêu R # -warning có ý nghĩa.

Hãy tưởng tượng bạn có một tập hợp các lớp thực thể mà bạn muốn tuần tự hóa, nói với Xml. Bạn có thể tạo một serializer cho việc này bằng cách sử dụng new XmlSerializerFactory().CreateSerializer(typeof(SomeClass)), nhưng sau đó bạn sẽ phải tạo một serializer riêng cho từng loại. Sử dụng generic, bạn có thể thay thế bằng cái sau, cái mà bạn có thể đặt trong một lớp chung mà các thực thể có thể xuất phát từ:

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

Vì có thể bạn không muốn tạo một bộ nối tiếp mới mỗi lần bạn cần tuần tự hóa một thể hiện của một loại cụ thể, bạn có thể thêm điều này:

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

Nếu lớp này KHÔNG chung chung, thì mỗi phiên bản của lớp sẽ sử dụng như nhau _typeSpecificSerializer.

Tuy nhiên, vì nó chung chung, một tập hợp các thể hiện có cùng loại Tsẽ chia sẻ một thể hiện duy nhất _typeSpecificSerializer(sẽ được tạo cho loại cụ thể đó), trong khi các phiên bản có loại khác Tsẽ sử dụng các phiên bản khác nhau _typeSpecificSerializer.

Một ví dụ

Cung cấp hai lớp mở rộng 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; }
}

... Hãy sử dụng chúng:

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

Trong trường hợp này, dưới mui xe, firstInstsecondInstsẽ là các thể hiện của cùng một lớp (cụ thể SerializableEntity<MyFirstEntity>), và như vậy, chúng sẽ chia sẻ một thể hiện của _typeSpecificSerializer.

thirdInstfourthInstlà các thể hiện của một lớp khác ( SerializableEntity<OtherEntity>) và do đó sẽ chia sẻ một thể _typeSpecificSerializerhiện khác với hai lớp kia.

Điều này có nghĩa là bạn nhận được các phiên bản tuần tự hóa khác nhau cho từng loại thực thể của mình , trong khi vẫn giữ chúng tĩnh trong ngữ cảnh của từng loại thực tế (nghĩa là được chia sẻ giữa các phiên bản thuộc loại cụ thể).


Do các quy tắc khởi tạo tĩnh (trình khởi tạo tĩnh không được gọi cho đến khi lớp được tham chiếu lần đầu tiên), bạn có thể từ bỏ kiểm tra trong Getter và chỉ khởi tạo nó trong khai báo thể hiện tĩnh.
Michael Brown
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.