2013-03-07 29 views
8

Kendimi pek çok değişmez sınıf oluşturmak zorundayım ve gereksiz bilgi olmadan bunu yapmanın bir yolunu bulmak istiyorum. Anonim bir yazı kullanamıyorum çünkü bu sınıfları yöntemlerden döndürmem gerekiyor. Ben intellisense desteği istiyorum, bu yüzden Sözlükler, dinamik ya da bunun gibi bir şey kullanmamayı tercih ederim. Ayrıca, Tuple <> 'u dışlayan iyi adlandırılmış özellikler istiyorum. Şimdiye kadar, bazı desenler denedim:C# içinde değişmez bir sınıf oluşturmanın en kısa yolu nedir?

// inherit Tuple<>. This has the added benefit of giving you Equals() and GetHashCode() 
public class MyImmutable : Tuple<int, string, bool> { 
    public MyImmutable(int field1, string field2, bool field3) : base(field1, field2, field3) { } 

    public int Field1 { get { return this.Item1; } } 
    public string Field2 { get { return this.Item2; } } 
    public bool Field3 { get { return this.Item3; } } 
} 

/////////////////////////////////////////////////////////////////////////////////// 

// using a custom SetOnce<T> struct that throws an error if set twice or if read before being set 
// the nice thing about this approach is that you can skip writing a constructor and 
// use object initializer syntax. 
public class MyImmutable { 
    private SetOnce<int> _field1; 
    private SetOnce<string> _field2; 
    private SetOnce<bool> _field3; 


    public int Field1 { get { return this._field1.Value; } set { this._field1.Value = value; } 
    public string Field2 { get { return this._field2.Value; } set { this._field2.Value = value; } 
    public bool Field3 { get { return this._field3.Value; } set { this._field3.Value = value; } 
} 

/////////////////////////////////////////////////////////////////////////////////// 

// EDIT: another idea I thought of: create an Immutable<T> type which allows you to 
// easily expose types with simple get/set properties as immutable 
public class Immutable<T> { 
    private readonly Dictionary<PropertyInfo, object> _values;  

    public Immutable(T obj) { 
     // if we are worried about the performance of this reflection, we could always statically cache 
     // the getters as compiled delegates 
     this._values = typeof(T).GetProperties() 
      .Where(pi => pi.CanRead) 
      // Utils.MemberComparer is a static IEqualityComparer that correctly compares 
      // members so that ReflectedType is ignored 
      .ToDictionary(pi => pi, pi => pi.GetValue(obj, null), Utils.MemberComparer); 
    } 

    public TProperty Get<TProperty>(Expression<Func<T, TProperty>> propertyAccessor) { 
     var prop = (PropertyInfo)((MemberExpression)propertyAccessor.Body).Member; 
     return (TProperty)this._values[prop]; 
    } 
} 

// usage 
public class Mutable { int A { get; set; } } 

// we could easily write a ToImmutable extension that would give us type inference 
var immutable = new Immutable<Mutable>(new Mutable { A = 5 }); 
var a = immutable.Get(m => m.A); 

// obviously, this is less performant than the other suggestions and somewhat clumsier to use. 
// However, it does make declaring the immutable type quite concise, and has the advantage that we can make 
// any mutable type immutable 

/////////////////////////////////////////////////////////////////////////////////// 

// EDIT: Phil Patterson and others mentioned the following pattern 
// this seems to be roughly the same # characters as with Tuple<>, but results in many 
// more lines and doesn't give you free Equals() and GetHashCode() 
public class MyImmutable 
{ 
    public MyImmutable(int field1, string field2, bool field3) 
    { 
     Field1 = field1; 
     Field2 = field2; 
     Field3 = field3; 
    } 

    public int Field1 { get; private set; } 
    public string Field2 { get; private set; } 
    public bool Field3 { get; private set; } 
} 

Bunlar, salt okunur alanları yaratan yapıcı aracılığıyla onları ayarlamak ve özellikleri aracılığıyla onları teşhir "standart" desen daha hem biraz daha az ayrıntılıdır. Bununla birlikte, her iki yöntemde hala çok miktarda boilerplate var.

Herhangi bir fikrin var mı?

+2

İkinci formunuz değişmez değil - 'Field1' öğesini getirip ardından ayarlayarak ve sonra yeniden getirerek bir değişiklik gözlemleyebilirsiniz. ('SetOnce', tanımlanmamış bir kümeden önce getirmeyi engellemezse.) –

+0

@JonSkeet SetOnce, ayarlanmadan önce getirmeyi engeller (ben gönderiyi gönderirim). – ChaseMedallion

+1

Ne yazık ki, ortaya çıkardığınız problemin slam-dunk çözümü yoktur. Dil tasarım ekibi bunun farkında. Umarım bu bir gün ele alınacaktır. –

cevap

1

Durumunuz için public {get;private set;} özelliklerinin işe yarayıp yaramadığını görün - ayrı alan bildirimlerinden biraz daha kompakt, tam olarak aynı anlamsal.

Güncelleme: ChaseMedallion'ın yorumladığı gibi ve bu yaklaşım, Tuple yaklaşımından farklı olarak otomatik olarak GetHashCode ve Equals yöntemlerini sağlamaz. güvenle ilginç bağlamlarda kullanılan olabilir nesneyi verir ve güzel isimleri sağladığından

class MyImmutable 
{ 
    public int MyProperty {get; private set;} 

    public MyImmutable(int myProperty) 
    { 
     MyProperty = v; 
    } 
} 

Ben Tuple yaklaşım gibi. Ben bu tür birçok türleri oluşturmak gerekir ben Tuple sınıfları yeniden düzenlemesini ele alacak:

  • inşaat zamanda objenin parçası koleksiyonları/dizeleri sınırsız çek önlemek için önceden hesaplamak GetHashCode ve mağaza olarak. Muhtemelen isteğe bağlı olarak, Dictionary numaralı anahtarlarda anahtar olarak kullanılan durumları etkinleştirmeye izin vermek.
  • genel adları (yani protected ya da sadece EditorBrowsableAttribute ile gizlilikten gizlen), böylece 2 ad grubu hakkında bir karışıklık olmayacaktır.

Yan not .../FxCop kuralları oluşturur Debug değişmez olduğu alan türlerini uygulamak göz önünde bulundurun: Eric Lippert'ın en series on immutable types göz atın. kolayca yeni iletmenin için değişken olmayan bir tip örneğinden yüklü veya kopyalanabilir içerikleri karşılık gelen bir değişken bir sınıf var olması durumunda

4

için

public class MyImmutable 
{ 
    public MyImmutable(int field1, string field2, bool field3) 
    { 
     Field1 = field1; 
     Field2 = field2; 
     Field3 = field3; 
    } 

    public int Field1 { get; private set; } 
    public string Field2 { get; private set; } 
    public bool Field3 { get; private set; } 
} 
+0

Sadece bu mülk türünü kullanıyorum. Daha az kod yazın ve üyeleri aynı modda yönetin daha iyi .. –

+0

Bu desenin farkındayım, ama Tuple <> modeli gibi ayrıntılı görünmekle birlikte, daha fazla satırı ele geçiriyor ve size ulaşmıyor Eşittir() ve GetHashCode() – ChaseMedallion

1

Immutable sınıflar en yararlı olan özel belirleyiciler ile otomatik özellikleri kullanabilir -type örneği. Eğer bir değişmez nesneye 2-3 'den fazla' 'değişiklik' 'yapması gerekiyorsa (yani referansı, orijinalinden uygun olan bir değişmeze işaret edecek şekilde değiştiriniz), verileri değişebilir bir nesneye kopyalayarak, değiştirerek, ve geri depolanması genellikle daha pratik sonra

güzel kolayca genel bir yaklaşım bir tanımlamaktır vb başka bir şekilde farklıdır yeni bir nesne oluşturma, bir şekilde orijinal farklı bir nesne oluşturma daha (muhtemelen iç) maruz alan-alan yapı tipi ve daha sonra hem değişken hem de değişmez sınıfların bu tip bir alanı tutması gerekir. Erişim özellikleri, her iki sınıf türü için ayrı ayrı tanımlanmalıdır, ancak altta yatan yapının ilgili yöntemlerine her iki tür de 'GetHashCode ve Equals yöntemler zincirine sahip olabilir.Bir diğerinin bir örneği verilen türden bir örnek oluşturmak, tüm üyeleri tek tek kopyalamak yerine tek bir yapı ataması kullanarak gerçekleştirilebilir.

Programcılar, yapıların nasıl çalıştığını anlarlarsa (bu tür bilgileri temel alsalar bile, diğerleri katılmasa bile) yapı tipini herkese açık hale getirebilir. Değişken bir tutucu sınıfa sahip olmak, yapı genel olsa bile yararlı olabilir, çünkü referans semantiklerinin yararlı olduğu zamanlar vardır (örneğin, Dictionary'da saklanan bir şeyi Dictionary'un kendisini değiştirmek zorunda kalmadan değiştirmek isteyebilir), ancak dönüştürülecek/değiştirilebilen/değiştirilemeyen model, yapı türleriyle sınıf türlerinden daha iyi çalışır.

İlgili konular