2010-03-13 20 views
7

.NET 3.5 kullanıyorum. Otomatik olarak oluşturulmuş ve kontrolüm dışında bazı karmaşık üçüncü taraf sınıflarımız var, ancak test amaçlı çalışmak zorundayız. Ekibimin test kodumuzda çok fazla iç içe geçmiş mülk sahibi olmasını/ayarlandığını görüyorum ve gayet hantal oluyor.C# özelliklerini ve zincirleme yöntemlerini akıcı şekilde ayarlama

Sorunu çözmek için, hiyerarşik ağaçtaki çeşitli nesnelerdeki özellikleri ayarlamak için akıcı bir arabirim oluşturmak istiyorum. Bu üçüncü taraf kitaplığında çok sayıda özellik ve sınıf vardır ve her şeyi el ile eşleştirmek çok yorucu olacaktır.

İlk düşüncem sadece nesne başlatıcılarını kullanmaktı. Red, Blue ve Green özellikleridir ve Mix(), Color dördüncü özelliğini bu karışık renkle en yakın RGB güvenli rengine ayarlayan bir yöntemdir. Boyalar, kullanmadan önce Stir() ile homojenleştirilmelidir.

Paint başlatmak için çalışır, ancak ben zinciri Mix() ve buna diğer yöntemlere ihtiyaç
Bucket b = new Bucket() { 
    Paint = new Paint() { 
    Red = 0.4; 
    Blue = 0.2; 
    Green = 0.1; 
    } 
}; 

. Sonraki girişimi: Ben ayarlamak istediğiniz her özellik için bir yöntem tanımlamak zorundayız çünkü

Create<Bucket>(Create<Paint>() 
    .SetRed(0.4) 
    .SetBlue(0.2) 
    .SetGreen(0.1) 
    .Mix().Stir() 
) 

Ama bu, iyi ölçek değildir ve tüm sınıflarında farklı özellikler yüzlerce vardır. Ayrıca, C# C# 4'ten önceki yöntemleri dinamik olarak tanımlamanın bir yolu yoktur, bu yüzden bir şekilde otomatik olarak bunu yapmak için bir şeyler yapabileceğimi düşünmüyorum.

Üçüncü girişimi: çok kötü baktığında o mümkün olacak gibi görünüyor değil

Create<Bucket>(Create<Paint>().Set(p => { 
    p.Red = 0.4; 
    p.Blue = 0.2; 
    p.Green = 0.1; 
    }).Mix().Stir() 
) 

. Bu tavsiye edilebilir bir yaklaşım mı? Bu şekilde çalışan bir Set yöntemi yazmak mümkün mü? Yoksa alternatif bir strateji mi takip etmeliyim?

+0

Aslında projelerimden birinde benzer bir yaklaşım var. –

cevap

9

Bu işe yarar mı?

Bucket b = new Bucket() { 
    Paint = new Paint() { 
    Red = 0.4; 
    Blue = 0.2; 
    Green = 0.1; 
    }.Mix().Stir() 
}; 

varsayılarak Mix() ve Stir() bir Paint nesneyi döndürmek için tanımlandığı gibidir. tarif edildiği gibi benzer kullanılabilir

public static T Init<T>(this T @this, Action<T> initAction) { 
    if (initAction != null) 
     initAction(@this); 
    return @this; 
} 

(ayarlama): void dönüş yöntemleri aramak için

, sen geçmek nesne ek başlatma gerçekleştirmek sağlayacak bir uzantısı yöntemi kullanabilir :

Sen aslında bir Bucket dönmek zincirindeki son yöntem istiyorum: Ben bunu bu şekilde düşünürdüm

Bucket b = new Bucket() { 
    Paint = new Paint() { 
    Red = 0.4; 
    Blue = 0.2; 
    Green = 0.1; 
    }.Init(p => { 
    p.Mix().Stir(); 
    }) 
}; 
+0

Geriye doğru bakıldığında, bu açık bir yaklaşım gibi görünüyor. Ağaçların ormana bu kadar uzun süre bakmasını özlemeliyim! –

+0

FYI, bu @ ile şaşkın olanlar için: '' '," aşağıdaki belirteci bir tamsayı tanımlayıcı olarak ele alır "(bir anahtar kelime olarak değil). Efekt, 'this' adlı bir parametrenin isimlendirilmesidir. –

+1

Şaka yapmıyorum, ama 'self' veya' source' gibi eşdeğer bir isim kullanabilmeniz için derleyiciyle savaşmak için bir neden göremiyorum. Aksi halde, iyi bir cevap ve +1. – Aaronaught

4

. Senin durumunda, sana Stir gibi bu yöntem sonradan

public class BucketBuilder 
{ 
    private int _red = 0; 
    private int _green = 0; 
    private int _blue = 0; 

    public Bucket Mix() 
    { 
     Bucket bucket = new Bucket(_paint); 
     bucket.Mix(); 
     return bucket; 
    } 
} 

Yani Mix aramadan önce() en az bir rengini ayarlamak gerekir) (,) kepçe Mix (olmak istiyorum düşünüyorum. Bunu bazı Syntax arayüzleri ile zorlayalım.

public interface IStillNeedsMixing : ICanAddColours 
{ 
    Bucket Mix(); 
} 

public interface ICanAddColours 
{ 
    IStillNeedsMixing Red(int red); 
    IStillNeedsMixing Green(int green); 
    IStillNeedsMixing Blue(int blue); 
} 

Ve zinciri

public static class CreateBucket 
{ 
    public static ICanAddColours UsingPaint 
    { 
     return new BucketBuilder(); 
    } 
} 

başlaması bir başlangıç ​​statik özelliği gerekir Ve bu kadar hoş çok Şimdi

public class BucketBuilder : IStillNeedsMixing, ICanAddColours 
{ 
    private int _red = 0; 
    private int _green = 0; 
    private int _blue = 0; 

    public IStillNeedsMixing Red(int red) 
    { 
     _red += red; 
     return this; 
    } 

    public IStillNeedsMixing Green(int green) 
    { 
     _green += green; 
     return this; 
    } 

    public IStillNeedsMixing Blue(int blue) 
    { 
     _blue += blue; 
     return this; 
    } 

    public Bucket Mix() 
    { 
     Bucket bucket = new Bucket(new Paint(_red, _green, _blue)); 
     bucket.Mix(); 
     return bucket; 
    } 
} 

, artık var en BucketBuilder bu uygulayalım İsteğe bağlı RGB parametreleriyle (en az bir tane girdiğiniz sürece) akıcı arayüz.
CreateBucket.UsingPaint.Red(0.4).Green(0.2).Mix().Stir(); 

Akıcı Arabirimlerle şey

birlikte koymak o kadar kolay değil, ama onlar karşı koduna geliştirici için kolaydır ve çok genişletilebilir olmasıdır. Tüm arama kodunuzu değiştirmeden buna bir Matt/Gloss bayrağı eklemek istiyorsanız, bunu yapmak çok kolay. Ayrıca, API'nizin sağlayıcınız, altınızdaki her şeyi değiştirirse, yalnızca bu tek kodu yeniden yazmanız gerekiyor demektir; tüm callin kodları aynı kalabilir.

0

Init uzantı yöntemini kullanırım, çünkü U her zaman delege ile oynayabilir. Cehennem Hep kolaylıkla yapabilirsiniz Bu şekilde uzatma ifadelerini almak ve hatta expresions ile oynamak yöntemleri (modifiye, ilerisi için saklamak, her neyse) ilan edebilir gibi mağaza varsayılan olan gruplar:

Create<Paint>(() => new Paint{p.Red = 0.3, p.Blue = 0.2, p.Green = 0.1}). 
Init(p => p.Mix().Stir()) 

This Way You Tüm eylemleri (veya funcs) kullanabilir ve standart başlatıcıları daha sonra için ifade zincirleri olarak önbelleğe alabilir?

0

Bir ton kodu yazmak zorunda kalmadan özellik ayarlarını zincirlemek istiyorsanız, bunu yapmanın bir yolu kod oluşturma (CodeDom) kullanmak olacaktır. Yansıma özelliklerinin bir listesini almak için Yansıma'yı kullanabilirsiniz, oluşturmaya çalıştığınız sınıfı döndüren son Build() yöntemiyle akıcı bir oluşturucu sınıfı oluşturun.

Özel araçların nasıl kaydedileceğiyle ilgili olarak tüm boilerplate nesnelerini atlayacağım - belgelerini bulmak oldukça kolay ama yine de uzun soluklu ve bunu ekleyerek çok fazla ekleyeceğimi sanmıyorum . Kodgen için ne düşündüğümü size göstereceğim.

public static class PropertyBuilderGenerator 
{ 
    public static CodeTypeDeclaration GenerateBuilder(Type destType) 
    { 
     if (destType == null) 
      throw new ArgumentNullException("destType"); 
     CodeTypeDeclaration builderType = new 
      CodeTypeDeclaration(destType.Name + "Builder"); 
     builderType.TypeAttributes = TypeAttributes.Public; 
     CodeTypeReference destTypeRef = new CodeTypeReference(destType); 
     CodeExpression resultExpr = AddResultField(builderType, destTypeRef); 
     PropertyInfo[] builderProps = destType.GetProperties(
      BindingFlags.Instance | BindingFlags.Public); 
     foreach (PropertyInfo prop in builderProps) 
     { 
      AddPropertyBuilder(builderType, resultExpr, prop); 
     } 
     AddBuildMethod(builderType, resultExpr, destTypeRef); 
     return builderType; 
    } 

    private static void AddBuildMethod(CodeTypeDeclaration builderType, 
     CodeExpression resultExpr, CodeTypeReference destTypeRef) 
    { 
     CodeMemberMethod method = new CodeMemberMethod(); 
     method.Attributes = MemberAttributes.Public | MemberAttributes.Final; 
     method.Name = "Build"; 
     method.ReturnType = destTypeRef; 
     method.Statements.Add(new MethodReturnStatement(resultExpr)); 
     builderType.Members.Add(method); 
    } 

    private static void AddPropertyBuilder(CodeTypeDeclaration builderType, 
     CodeExpression resultExpr, PropertyInfo prop) 
    { 
     CodeMemberMethod method = new CodeMemberMethod(); 
     method.Attributes = MemberAttributes.Public | MemberAttributes.Final; 
     method.Name = prop.Name; 
     method.ReturnType = new CodeTypeReference(builderType.Name); 
     method.Parameters.Add(new CodeParameterDeclarationExpression(prop.Type, 
      "value")); 
     method.Statements.Add(new CodeAssignStatement(
      new CodePropertyReferenceExpression(resultExpr, prop.Name), 
      new CodeArgumentReferenceExpression("value"))); 
     method.Statements.Add(new MethodReturnStatement(
      new CodeThisExpression())); 
     builderType.Members.Add(method); 
    } 

    private static CodeFieldReferenceExpression AddResultField(
     CodeTypeDeclaration builderType, CodeTypeReference destTypeRef) 
    { 
     const string fieldName = "_result"; 
     CodeMemberField resultField = new CodeMemberField(destTypeRef, fieldName); 
     resultField.Attributes = MemberAttributes.Private; 
     builderType.Members.Add(resultField); 
     return new CodeFieldReferenceExpression(
      new CodeThisReferenceExpression(), fieldName); 
    } 
} 

Bence bu should bunu hemen - Açıkçası denenmemiş, ama buradan nereye gittiğini türlerinin listesi ile doldurulan bir CodeCompileUnit derler ( BaseCodeGeneratorWithSite devralmasını) bir Codegen oluşturmaktır. Bu liste, araçla kaydettiğiniz dosya türünden geliyor - bu durumda, muhtemelen oluşturucu kodu oluşturmak istediğiniz satır sınırlandırılmış bir liste listesiyle bir metin dosyası yapıyorum. Takımın bunu taramasını sağlayın, türleri yükleyin (önce montajları yüklemiş olabilir) ve bayt kodu oluşturun.

göründüğü gibi sert, ama zor değil ve işiniz bittiğinde böyle kod yazmak mümkün olacak: İnanıyorum

Paint p = new PaintBuilder().Red(0.4).Blue(0.2).Green(0.1).Build().Mix.Stir(); 

neredeyse tam istediğiniz şeydir.Kod nesil çağırmak için yapmanız gereken o türlerinin bir listesini, (en .buildertypes diyelim) özel uzantılı aracı kayıt projenizde bu uzantılı bir dosya koyun ve koyun geçerli:

MyCompany.MyProject.Paint 
MyCompany.MyProject.Foo 
MyCompany.MyLibrary.Bar 

Ve bunun gibi. Kaydettiğinizde, yukarıdaki gibi yazma ifadelerini destekleyen ihtiyaç duyduğunuz kod dosyasını otomatik olarak oluşturur.

Bu yaklaşımı, birkaç yüz farklı ileti türüyle son derece karmaşık bir ileti sistemi için kullanmıştım. Her zaman mesajı oluşturmak, bir grup özellik belirlemek, kanaldan göndermek, kanaldan almak, yanıtı serileştirmek, vb. Çok uzun sürüyordu. Bağımsız özelliklerin tümünü bağımsız değişken olarak alan ve doğru türden bir yanıtı geri alan tek mesajlaşma sınıfı. Bu herkese tavsiye edeceğim bir şey değil, ama çok büyük projelerle uğraşırken, bazen kendi sözdizimini icat etmeye başlamanız gerekiyor!