2010-03-13 5 views
7

मैं .NET 3.5 का उपयोग कर रहा हूं। हमारे पास कुछ जटिल तृतीय-पक्ष कक्षाएं हैं जो स्वचालित रूप से जेनरेट की जाती हैं और मेरे नियंत्रण से बाहर होती हैं, लेकिन हमें परीक्षण उद्देश्यों के लिए काम करना चाहिए। मैं देखता हूं कि मेरी टीम हमारे परीक्षण कोड में बहुत गहरी नींव वाली संपत्ति प्राप्त कर रही है/सेटिंग कर रही है, और यह बहुत बोझिल हो रही है।सी # गुणों और चेनिंग विधियों को स्पष्ट रूप से सेट करना

समस्या का समाधान करने के लिए, मैं पदानुक्रमित पेड़ में विभिन्न वस्तुओं पर गुणों को सेट करने के लिए एक धाराप्रवाह इंटरफ़ेस बनाना चाहता हूं। इस तीसरे पक्ष की लाइब्रेरी में बड़ी संख्या में गुण और कक्षाएं हैं, और मैन्युअल रूप से सब कुछ मैप करने के लिए यह बहुत कठिन होगा।

मेरा प्रारंभिक विचार केवल ऑब्जेक्ट प्रारंभकर्ताओं का उपयोग करना था। Red, Blue, और Green गुण हैं, और Mix() एक ऐसी विधि है जो चौथी संपत्ति Color को उस मिश्रित रंग के साथ निकटतम आरजीबी-सुरक्षित रंग में सेट करती है। उपयोग किए जाने से पहले पेंट्स को Stir() के साथ homogenized किया जाना चाहिए।

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

Paint प्रारंभ करने में काम करता है यही कारण है कि, लेकिन मैं श्रृंखला Mix() और इसे करने के लिए अन्य तरीकों की जरूरत है। अगला प्रयास:

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

लेकिन वह अच्छी तरह से बड़े पैमाने नहीं है, क्योंकि मैं प्रत्येक प्रॉपर्टी मैं सेट करना चाहते हैं के लिए एक विधि को परिभाषित करना होगा, और सभी वर्गों में विभिन्न गुणों के सैकड़ों रहे हैं। इसके अलावा, सी # में सी # 4 से पहले विधियों को गतिशील रूप से परिभाषित करने का कोई तरीका नहीं है, इसलिए मुझे नहीं लगता कि मैं कुछ तरीकों से इसे स्वचालित रूप से करने के लिए चीजों में लगा सकता हूं।

तीसरा प्रयास:

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

कि बहुत बुरा नहीं लगती है, और लगता है जैसे कि यह संभव होगा। क्या यह एक सलाहकार दृष्टिकोण है? क्या Set विधि लिखना संभव है जो इस तरह से काम करता है? या क्या मुझे वैकल्पिक रणनीति का पीछा करना चाहिए?

+0

वास्तव में मेरे पास मेरी परियोजनाओं में से एक में समान दृष्टिकोण है। –

उत्तर

9

क्या यह काम करता है?

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

मान लिया जाये कि Mix() और Stir() एक Paint वस्तु लौटने के लिए परिभाषित कर रहे हैं।

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

कौन सा समान इस्तेमाल किया जा सकता सेट करने के लिए() के रूप में वर्णित:

तरीकों कि वापसी void फोन के लिए, आप एक विस्तार विधि है कि आप वस्तु आप में पारित पर अतिरिक्त प्रारंभ करने की अनुमति देगा का उपयोग कर सकते :

आप अनिवार्य रूप से एक बाल्टी वापस जाने के लिए श्रृंखला में अपने पिछले विधि हैं:

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

पीछे की ओर, यह एक स्पष्ट दृष्टिकोण की तरह लगता है। वृक्षों के लिए जंगल को लापता होना बहुत लंबे समय तक इस पर घूरने से लापता होना चाहिए! –

+0

'@ this' द्वारा परेशान लोगों के लिए FYI: '@' का अर्थ है "निम्नलिखित टोकन को शाब्दिक पहचानकर्ता के रूप में मानें" (एक कीवर्ड के रूप में नहीं)। प्रभाव 'इस' नामक पैरामीटर को नाम देना है। –

+1

मैं इसके द्वारा परेशान नहीं हूं, लेकिन जब आप 'self' या 'source' जैसे समकक्ष नाम का उपयोग कर सकते हैं तो मुझे कंपाइलर से लड़ने का कोई कारण नहीं दिखता है। अन्यथा, अच्छा जवाब और +1। – Aaronaught

4

मैं इसके बारे में इस तरह से सोचते हैं। आपके मामले में, मुझे लगता है कि आपको लगता है कि विधि) के रूप में आप चलाते कर सकते हैं) मिक्स (हो सकता है, (बाल्टी बाद में

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

तो तुम इससे पहले कि आप मिक्स फोन() कम से कम एक रंग निर्धारित करने की आवश्यकता चाहते हैं। आइए कुछ सिंटेक्स इंटरफेस के साथ मजबूर करें।

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

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

और

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

अब आप एक आरंभिक स्थिर संपत्ति की जरूरत श्रृंखला

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

शुरू करने के लिए और वह काफी यह है, अब आप एक है के BucketBuilder करने के लिए इन लागू कर सकते हैं वैकल्पिक आरजीबी पैरामीटर के साथ धाराप्रवाह इंटरफ़ेस (जब तक आप कम से कम एक दर्ज करते हैं) बोनस के रूप में।

CreateBucket.UsingPaint.Red(0.4).Green(0.2).Mix().Stir(); 

Fluent इंटरफेस के साथ बात यह है कि वे कहते हैं कि एक साथ रखा करने के लिए आसान नहीं हैं, लेकिन वे के खिलाफ कोड के लिए डेवलपर के लिए आसान कर रहे हैं और वे बहुत विस्तृत हो रहा है। यदि आप अपने सभी कॉलिंग कोड को बदले बिना मैट/ग्लॉस ध्वज जोड़ना चाहते हैं, तो यह करना आसान है।

इसके अलावा, यदि आपके एपीआई का प्रदाता आपके नीचे सबकुछ बदलता है, तो आपको केवल कोड के एक टुकड़े को फिर से लिखना होगा; सभी कॉलिन कोड वही रह सकते हैं।

0

मैं इनिट एक्सटेंशन विधि का उपयोग करूंगा क्योंकि यू हमेशा प्रतिनिधि के साथ खेल सकता है। नरक तुम हमेशा विस्तार तरीकों कि भाव का समय लग और यहां तक ​​कि expresions के साथ खेलते हैं (उन्हें, बाद के लिए की दुकान को संशोधित, जो कुछ भी) घोषणा कर सकते हैं इस तरह से आप आसानी से कर सकते हैं की तरह दुकान डिफ़ॉल्ट grups:

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

इस तरह आप बाद में सभी क्रियाओं (या funcs) और मानक प्रारंभकर्ताओं को अभिव्यक्ति श्रृंखला के रूप में कैश कर सकते हैं?

0

यदि आप वास्तव में कोड कोड लिखने के बिना संपत्ति सेटिंग्स को चेन करने में सक्षम होना चाहते हैं, तो ऐसा करने का एक तरीका कोड जनरेशन (कोडडॉम) का उपयोग करना होगा। आप परिवर्तनीय गुणों की एक सूची प्राप्त करने के लिए प्रतिबिंब का उपयोग कर सकते हैं, एक अंतिम Build() विधि के साथ एक धाराप्रवाह बिल्डर वर्ग उत्पन्न करता है जो उस वर्ग को लौटाता है जिसे आप वास्तव में बनाने की कोशिश कर रहे हैं।

मैं कस्टम टूल को पंजीकृत करने के बारे में सभी बॉयलरप्लेट सामग्री को छोड़ने जा रहा हूं - यह दस्तावेज ढूंढना काफी आसान है लेकिन अभी भी लंबे समय तक हवादार है और मुझे नहीं लगता कि मैं इसे शामिल करके बहुत कुछ जोड़ रहा हूं । यद्यपि मैं कोडजन के लिए क्या सोच रहा हूं मैं आपको दिखाऊंगा।

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

मुझे लगता है कि यह होना चाहिए बस के बारे में यह कर - यह स्पष्ट रूप से अपरीक्षित है, लेकिन जहां तुम यहाँ से जाओ कि तुम एक codegen (BaseCodeGeneratorWithSite से इनहेरिट) है कि एक CodeCompileUnit प्रकार की एक सूची के साथ आबादी वाले संकलित पैदा करते हैं। वह सूची आपके द्वारा टूल के साथ पंजीकृत फ़ाइल प्रकार से आती है - इस मामले में मैं शायद इसे एक प्रकार की लाइन-डिलीमिटेड सूची के साथ एक टेक्स्ट फ़ाइल बनाउंगा जिसके लिए आप बिल्डर कोड जेनरेट करना चाहते हैं। उपकरण को स्कैन करें, प्रकारों को लोड करें (पहले असेंबली लोड करना पड़ सकता है), और बाइटकोड उत्पन्न करें।

यह रूप में यह लग रहा है कठिन है, लेकिन के रूप में कठिन नहीं है, और यह करने के बाद आप इस तरह कोड लिखने के लिए सक्षम हो जाएगा:

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

कौन सा मेरा मानना ​​है कि लगभग बिल्कुल तुम क्या चाहते है।तुम सब कोड पीढ़ी आह्वान करने के लिए क्या करना है दर्ज होने वाले कस्टम विस्तार के साथ उपकरण (मान लीजिए कि .buildertypes हैं), अपनी परियोजना में है कि एक्सटेंशन वाली फ़ाइल रखा, और डाल प्रकार की एक सूची में है:

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

और इसी तरह। जब आप सहेजते हैं, तो यह स्वचालित रूप से उस कोड फ़ाइल को जेनरेट करेगा जो आपको आवश्यक है जैसे ऊपर दिए गए लेखन विवरणों का समर्थन करता है।

मैंने कई सौ अलग-अलग संदेश प्रकारों के साथ अत्यधिक संकलित संदेश प्रणाली के लिए इस दृष्टिकोण का उपयोग किया है। हमेशा संदेश बनाने, गुणों का एक गुच्छा सेट करने, चैनल के माध्यम से भेजने, चैनल से प्राप्त करने, प्रतिक्रिया को क्रमबद्ध करने आदि के लिए बहुत लंबा समय लग रहा था ... कोडजन का उपयोग करके काम को बहुत सरल बना दिया गया क्योंकि यह मुझे उत्पन्न करने में सक्षम बनाता था एकल मैसेजिंग क्लास जिसने सभी व्यक्तिगत गुणों को तर्क के रूप में लिया और सही प्रकार की प्रतिक्रिया वापस थूक दिया। ऐसा कुछ नहीं है जो मैं सभी को सलाह दूंगा, लेकिन जब आप बहुत बड़ी परियोजनाओं से निपट रहे हों, तो कभी-कभी आपको अपने स्वयं के वाक्यविन्यास का आविष्कार करना शुरू करना होगा!