ReSharper uyarıyor: “Genel tipte statik alan”


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

Bu yanlış mı? Bunun aslında her bir örnek static readonlyiçin mümkün EnumRouteConstraint<T>olan her alan için bir alanı olduğunu varsayabilirim .


Bazen bir özellik, bazen bir sıkıntı. C # onları ayırt etmek için bazı anahtar kelime olmasını diledi
nawfal

Yanıtlar:


468

Tür argümanlarının her birleşimi için gerçekten bir alan elde edeceğinizi bildiğiniz sürece, genel bir türde statik bir alana sahip olmak iyidir. Tahminimce R # bunun farkında olmasanız bile sizi uyarıyor.

İşte bunun bir örneği:

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

Gördüğünüz gibi, Generic<string>.Foofarklı bir alan Generic<object>.Foo- ayrı değerler tutuyorlar.


Bu, genel sınıflar, statik türler içeren genel olmayan bir sınıftan miras aldığında da geçerlidir. Örneğin class BaseFoobir statik üye içeren oluşturursam , ondan türetilen class Foo<T>: BaseFootüm Foo<T>sınıflar aynı statik üye değerini paylaşır?
bikeman868

2
Burada kendi yorumumu yanıtlıyorum, ancak evet, eğer genel olmayan bir temel sınıfta bulunuyorsa, tüm Foo <T> aynı statik değere sahip olacaktır. Bkz dotnetfiddle.net/Wz75ya
bikeman868

147

Gönderen JetBrains wiki :

Vakaların büyük çoğunluğunda, genel tipte statik bir alana sahip olmak bir hatanın işaretidir. Bunun nedeni genel bir tip statik alan olacaktır değil farklı yakın inşa tiplerinin örnekleri arasında paylaşılabilir. Genel sınıf için o Bu araçlar C<T>statik alana sahip X, değerleri C<int>.Xve C<string>.X tamamen farklı, bağımsız değerleri vardır.

Eğer Nadiren do 'uzman' statik alanlar ihtiyaç uyarıyı bastırmak için çekinmeyin.

Farklı genel bağımsız değişkenlere sahip örnekler arasında paylaşılan bir statik alanınızın olması gerekiyorsa , statik üyelerinizi saklamak için genel olmayan bir temel sınıf tanımlayın , ardından genel türünüzü bu türden devralmak üzere ayarlayın.


13
Genel bir tür kullanırken, teknik olarak barındırdığınız her genel tür için ayrı ve ayrı bir sınıf elde edersiniz. İki ayrı, genel olmayan sınıf bildirirken, aralarında statik değişkenler paylaşmayı beklemezsiniz, o halde neden jenerikler farklı olsun ki? Bunun nadir görülmesinin tek yolu, geliştiricilerin çoğunluğunun genel sınıflar oluştururken ne yaptıklarını anlamamasıdır.
Syndog

2
@Genel bir sınıfta açıklanan statik davranışı benim için iyi ve anlaşılır görünüyor. Ancak sanırım bu uyarıların ardında yatan neden, her ekibin sadece geliştiricileri deneyimlememiş ve odaklamamış olmasıdır. Doğru kod, geliştiricinin nitelikleri nedeniyle hataya açık hale gelir.
Stas Ivanov

Ama sadece bu statik alanları tutmak için genel olmayan bir temel sınıf yapmak istemiyorsam. Bu durumda sadece uyarıları engelleyebilir miyim?
Tom Lint

@TomLint, ne yaptığınızı biliyorsanız, uyarıları bastırmak gerçekten yapılacak şeydir.
AakashM

65

Bu mutlaka bir hata değildir - C # jeneriklerinin olası yanlış anlaşılması hakkında sizi uyarır .

Jeneriklerin ne yaptığını hatırlamanın en kolay yolu şudur: Jenerikler sınıflar oluşturmak için "taslaklardır", tıpkı sınıflar gibi nesneler yaratmak için "taslaklardır". (Peki, bu bir basitleştirme. Yöntem jeneriklerini de kullanabilirsiniz.)

Bu bakış açısından MyClassRecipe<T>bir sınıf değil - sınıfınızın nasıl görüneceğine dair bir reçete, bir plan. T'yi som gibi bir şeyle, int, string vb. İle değiştirdiğinizde, bir sınıf alırsınız. Yeni oluşturulan sınıfınızda (diğer sınıflarda olduğu gibi) statik bir üyenin (alan, özellik, yöntem) bildirilmesi ve burada herhangi bir hata belirtisi olmaması tamamen yasaldır. static MyStaticProperty<T> Property { get; set; }Sınıf planınızda beyan ederseniz, ilk bakışta biraz şüpheli olur , ancak bu da yasaldır. Mülkünüz de parametreleştirilir veya geçici olarak belirlenir.

VB statiklerine şaşmamak gerekir shared. Ancak bu durumda, bu tür "paylaşılan" üyelerin yalnızca aynı sınıfın örnekleri arasında paylaşıldığını ve <T>başka bir şeyle değiştirerek üretilen farklı sınıflar arasında paylaşılmadığını bilmeniz gerekir .


1
Bence C ++ adı hepsinden anlaşılır. C ++ 'da bunlara Şablonlar denir, ki bunlar somut sınıflar için şablonlardır.
Michael Brown

8

Burada zaten uyarıyı ve nedenini açıklayan birkaç iyi cevap var. Bunların birçoğu jenerik tipte statik bir alana sahip olmak gibi bir şeyi genellikle bir hata olarak belirtir .

Bu özelliğin nasıl yararlı olabileceğine bir örnek ekleyeceğimi düşündüm, yani R # uyarısının bastırılmasının mantıklı olduğu bir durum.

Serileştirmek istediğiniz bir dizi varlık sınıfınız olduğunu düşünün, örneğin Xml. Bunu kullanarak bir serileştirici oluşturabilirsiniz new XmlSerializerFactory().CreateSerializer(typeof(SomeClass)), ancak daha sonra her tür için ayrı bir serileştirici oluşturmanız gerekir. Jenerikler kullanarak, bunu varlıkların türetebileceği genel bir sınıfa yerleştirebileceğiniz aşağıdakilerle değiştirebilirsiniz:

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

Muhtemelen belirli bir türdeki bir örneği her serileştirmeniz gerektiğinde yeni bir serileştirici oluşturmak istemediğiniz için şunu ekleyebilirsiniz:

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

Bu sınıf genel DEĞİLSE, sınıfın her örneği aynı kullanırdı _typeSpecificSerializer.

Ancak genel olduğu için, aynı türden bir örnek kümesi Ttek bir örneğini _typeSpecificSerializer(belirli bir tür için oluşturulacak) paylaşırken , farklı türdeki Törnekler farklı örneklerini kullanacaktır _typeSpecificSerializer.

Bir örnek

Şu iki sınıfı sağladı 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; }
}

... kullanalım:

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

Bu durumda, başlık altında, firstInstve secondInstaynı sınıf (yani örnekleri olur SerializableEntity<MyFirstEntity>), ve bu şekilde, bunlar bir örneğini paylaşır _typeSpecificSerializer.

thirdInstve fourthInstbaşka bir sınıf (örnekleridir SerializableEntity<OtherEntity>) ve bu yüzden bir örneğini paylaşacak _typeSpecificSerializeryani farklı diğer ikisi.

Bu, varlık türlerinizin her biri için farklı serileştirici örnekleri aldığınız anlamına gelir , ancak bunları her gerçek türün bağlamında statik tutar (yani, belirli bir türdeki örnekler arasında paylaşılır).


Statik başlatma kuralları nedeniyle (sınıf ilk başvurulanana kadar statik başlatıcı çağrılmaz) Getter'de denetimi bırakabilir ve yalnızca statik örnek bildiriminde başlatabilirsiniz.
Michael Brown
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.