2011-11-17 11 views
14

यह मेरा एक पूर्व प्रश्न से संबंधित है C# Generic List conversion to Class implementing List<T>एक सूची <T> जहां टी बनाने के लिए LINQ का उपयोग करना: SomeClass <U>

मैं निम्नलिखित कोड है: यह काम करता है लेकिन मैं होना चाहते हैं

public abstract class DataField 
{ 
    public string Name { get; set; } 
} 

public class DataField<T> : DataField 
{ 
    public T Value { get; set; } 
} 

public static List<DataField> ConvertXML(XMLDocument data) { 
    result = (from d in XDocument.Parse(data.OuterXML).Root.Decendendants() 
         select new DataField<string> 
         { 
          Name = d.Name.ToString(), 
          Value = d.Value 
         }).Cast<DataField>().ToList(); 
    return result; 
} 

LINQ क्वेरी के चयन भाग को इस तरह कुछ करने के लिए संशोधित करने में सक्षम:

select new DataField<[type defined in attribute of XML Element]> 
{ 
    Name = d.Name.ToString(), 
    Value = d.Value 
} 

क्या यह सिर्फ एक खराब दृष्टिकोण है? क्या यह संभव है? कोई सुझाव?

+3

मुझे लगता है आप को पता है DataField किस उपवर्ग आप वैसे भी कभी नहीं जा रहे हैं की तरह है, तो क्यों न सिर्फ 'वर्ग DataField {स्ट्रिंग नाम का उपयोग करें; ऑब्जेक्ट वैल्यू;} '? –

+0

मैं वास्तव में ऑब्जेक्ट के रूप में सभी मानों को संदर्भित नहीं करना चाहता हूं। अगर मैं एक समान दृष्टिकोण लेना चाहता था, तो क्लास डेटाफिल्ड {स्ट्रिंग नाम का उपयोग करना आसान लगेगा; स्ट्रिंग प्रकार; स्ट्रिंग मान}। –

+1

निश्चित रूप से, लेकिन फिर आपको इसका उपयोग करने से पहले हर बार अपने डेटा को पार्स करना होगा। यदि आप ऑब्जेक्ट में डेटा को प्री-पार्स करते हैं, और फिर उन्हें डेटा फ़ील्ड में स्टोर करते हैं, तो आप उन्हें इस्तेमाल कर सकते हैं जब आप उनका उपयोग करने के लिए तैयार हों। यह ADO.NET डेटा रीडर के साथ करता है (हालांकि डेटा रीडर कॉलम मानों की आवश्यकता होने तक पार्सिंग के निष्पादन में देरी करता है)। उदाहरण के लिए, 'अगर (myField.Value डेटटाइम है {/ * उस तारीख की चीज़ * /} 'या' if (myField.Name == "importantDateThing") {var date = (dateTime) myField.Value;/* महत्वपूर्ण है तिथि बात * /} ' –

उत्तर

8

यहाँ एक काम कर समाधान है: (आप अपने प्रकार विशेषता के लिए पूरी तरह से योग्य प्रकार के नाम का उल्लेख करना होगा अन्यथा आप किसी भी तरह एक मानचित्रण कॉन्फ़िगर करने के लिए की है। ..)

मैं गतिशील कीवर्ड का इस्तेमाल किया है, तो आप मान सेट करने के प्रतिबिंब का उपयोग कर सकते बजाय यदि आप सी # 4 नहीं है ...

public static void Test() 
{ 
    string xmlData = "<root><Name1 Type=\"System.String\">Value1</Name1><Name2 Type=\"System.Int32\">324</Name2></root>"; 

    List<DataField> dataFieldList = DataField.ConvertXML(xmlData); 

    Debug.Assert(dataFieldList.Count == 2); 
    Debug.Assert(dataFieldList[0].GetType() == typeof(DataField<string>)); 
    Debug.Assert(dataFieldList[1].GetType() == typeof(DataField<int>)); 
} 

public abstract class DataField 
{ 
    public string Name { get; set; } 

    /// <summary> 
    /// Instanciate a generic DataField<T> given an XElement 
    /// </summary> 
    public static DataField CreateDataField(XElement element) 
    { 
     //Determine the type of element we deal with 
     string elementTypeName = element.Attribute("Type").Value; 
     Type elementType = Type.GetType(elementTypeName); 

     //Instanciate a new Generic element of type: DataField<T> 
     dynamic dataField = Activator.CreateInstance(typeof(DataField<>).MakeGenericType(elementType)); 
     dataField.Name = element.Name.ToString(); 

     //Convert the inner value to the target element type 
     dynamic value = Convert.ChangeType(element.Value, elementType); 

     //Set the value into DataField 
     dataField.Value = value; 

     return dataField; 
    } 

    /// <summary> 
    /// Take all the descendant of the root node and creates a DataField for each 
    /// </summary> 
    public static List<DataField> ConvertXML(string xmlData) 
    { 
     var result = (from d in XDocument.Parse(xmlData).Root.DescendantNodes().OfType<XElement>() 
         select CreateDataField(d)).ToList(); 

     return result; 
    } 
} 

public class DataField<T> : DataField 
{ 
    public T Value { get; set; } 
} 
4

एक सामान्य वर्ग के विभिन्न उदाहरण वास्तव में अलग-अलग वर्ग हैं।
आईई। DataField<string> और DataField<int> सभी समान नहीं हैं (!)

इसका मतलब है कि आप रन-टाइम के दौरान जेनेरिक पैरामीटर को परिभाषित नहीं कर सकते हैं, क्योंकि इसे संकलन-समय के दौरान निर्धारित किया जाना है।

+1

यह बिल्कुल सही नहीं है। आप प्रतिबिंब का उपयोग कर रनटाइम पर सामान्य प्रकार बना सकते हैं। आप जो कहते हैं वह कैसा है जैसे सी ++ टेम्पलेट्स व्यवहार करते हैं। – svick

+0

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

5

आप आसानी से सी # में ऐसा नहीं कर सकते हैं। जेनेरिक प्रकार तर्क को संकलन समय पर निर्दिष्ट करना होता है। आप> अन्यथा

   int X = 1; 
      Type listype = typeof(List<>); 
      Type constructed = listype.MakeGenericType( X.GetType() ); 
      object runtimeList = Activator.CreateInstance(constructed); 

यहाँ हम अभी बनाया है एक सूची < पूर्णांक ऐसा करने के लिए प्रतिबिंब का उपयोग कर सकते हैं। आप यह असंभव के रूप में आप प्रतिबिंब के साथ ऐसा कर सकते हैं नहीं है प्रतिबिंब

var instance = Activator.CreateInstance(typeof(DataField) 
         .MakeGenericType(Type.GetType(typeNameFromAttribute)); 
    // and here set properties also by reflection 
3

आप सामान्य प्रकार बना सकते हैं के साथ कर सकते हैं। लेकिन यह जेनिक्स के लिए डिजाइन नहीं किया गया था और यह नहीं है कि यह किया जाना चाहिए। आप सामान्य प्रकार बनाने के लिए प्रतिबिंब का उपयोग करने के लिए जा रहे हैं, तो आप के रूप में अच्छी तरह से सब पर एक सामान्य प्रकार का उपयोग नहीं कर सकते हैं और सिर्फ निम्नलिखित वर्ग का उपयोग करें:

public class DataField 
{ 
    public string Name { get; set; } 
    public object Value { get; set; } 
} 
4

मैं कहूंगा कि यह एक गरीब दृष्टिकोण है। हकीकत में, आप अपनी एक्सएमएल फ़ाइल को पार्स करने के बाद भी, आपको यह नहीं पता चल जाएगा कि आपके पास किस प्रकार के "डेटाफिल्ड्स" हैं। आप उन्हें ऑब्जेक्ट्स के रूप में भी पार्स कर सकते हैं।

हालांकि, अगर आप जानते हैं कि आप ही कभी प्रकार के एक्स संख्या करने जा रहे हैं, तो आप ऐसा तरह कर सकते हैं:

var Dictionary<string, Func<string, string, DataField>> myFactoryMaps = 
{ 
    {"Type1", (name, value) => { return new DataField<Type1>(name, Type1.Parse(value); } }, 
    {"Type2", (name, value) => { return new DataField<Type2>(name, Type2.Parse(value); } }, 
}; 
+0

चालाक ... और मैं इसे ध्यान में रखूंगा। –

+0

अमूर्तता की शक्ति को कभी कम मत समझें। :) –

1

आप से डेटा प्रकार का निर्धारण करने के लिए तर्क सम्मिलित करना होगा अपनी XML और सभी प्रकार आप उपयोग करने की आवश्यकता को जोड़ने, लेकिन यह काम करना चाहिए:

  result = (from d in XDocument.Parse(data.OuterXML).Root.Descendants() 
         let isString = true //Replace true with your logic to determine if it is a string. 
         let isInt = false //Replace false with your logic to determine if it is an integer. 
         let stringValue = isString ? (DataField)new DataField<string> 
         { 
          Name = d.Name.ToString(), 
          Value = d.Value 
         } : null 
         let intValue = isInt ? (DataField)new DataField<int> 
         { 
          Name = d.Name.ToString(), 
          Value = Int32.Parse(d.Value) 
         } : null 
         select stringValue ?? intValue).ToList(); 
4

Termit के जवाब निश्चित रूप से उत्कृष्ट है। यहां एक छोटा संस्करण है।

 public abstract class DataField 
     { 
       public string Name { get; set; } 
     } 

     public class DataField<T> : DataField 
     { 
       public T Value { get; set; } 
       public Type GenericType { get { return this.Value.GetType(); } } 
     } 

     static Func<XElement , DataField> dfSelector = new Func<XElement , DataField>(e => 
     { 
       string strType = e.Attribute("type").Value; 
       //if you dont have an attribute type, you could call an extension method to figure out the type (with regex patterns) 
       //that would only work for struct 
       Type type = Type.GetType(strType); 
       dynamic df = Activator.CreateInstance(typeof(DataField<>).MakeGenericType(type)); 

       df.Name = e.Attribute("name").Value; 
       dynamic value = Convert.ChangeType(e.Value , type); 
       df.Value = value; 
       return df; 
     }); 

     public static List<DataField> ConvertXML(string xmlstring) 
     { 
       var result = XDocument.Parse(xmlstring) 
             .Root.Descendants("object") 
             .Select(dfSelector) 
             .ToList(); 
       return result; 
     } 


     static void Main(string[] args) 
     { 
       string xml = "<root><object name=\"im1\" type=\"System.String\">HelloWorld!</object><object name=\"im2\" type=\"System.Int32\">324</object></root>"; 

       List<DataField> dfs = ConvertXML(xml); 
     } 
2

@Termit और @Burnzy आगे अच्छा factory methods से जुड़े समाधान डाल दिया।

समस्या यह है कि आप संदिग्ध रिटर्न के लिए अतिरिक्त तर्क (अधिक परीक्षण, अधिक त्रुटियों) के समूह के साथ अपना पार्सिंग दिनचर्या लोड कर रहे हैं।

ऐसा करने का एक और तरीका टाइप किए गए पढ़ने के तरीकों के साथ सरलीकृत स्ट्रिंग-आधारित डेटाफिल्ड का उपयोग करना होगा - this प्रश्न के लिए शीर्ष उत्तर।

एक टाइप किया-मूल्य विधि का कार्यान्वयन और अच्छा होगा, लेकिन केवल मूल्य प्रकार के लिए काम करता है (जो तार शामिल नहीं है लेकिन datetimes शामिल करता है):

public T? TypedValue<T>() 
    where T : struct 
{ 
    try { return (T?) Convert.ChangeType(this.Value, typeof(T)); } 
    catch { return null; } 
} 


मुझे लगता है कि आप यह सोचते हैं हूँ ' क्षेत्र में उपयोगकर्ता-नियंत्रण को गतिशील रूप से असाइन करने जैसी चीजों को करने के लिए प्रकार की जानकारी का उपयोग करना चाहते हैं, सत्यापन नियम, दृढ़ता के लिए सही एसक्यूएल प्रकार आदि

मैंने इस तरह की चीजों को दृष्टिकोण के साथ किया है जो लगता है तुम्हारी तरह थोड़ा

दिन के अंत में आपको अपने कोड से अपने मेटाडेटा को अलग करना चाहिए - @ बर्जी का जवाब मेटाडेटा (डेटाफ़िल्ल्ड तत्व की "प्रकार" विशेषता) के आधार पर कोड चुनता है और इसका एक बहुत ही सरल उदाहरण है।

यदि आप एक्सएमएल से काम कर रहे हैं, तो एक्सएसडी मेटाडेटा का एक बहुत उपयोगी और एक्स्टेंसिबल रूप है।

जहाँ तक क्या आप में प्रत्येक फ़ील्ड के डेटा स्टोर के रूप में - क्योंकि उपयोग तार:

  • वे नल
  • वे
  • वे अमान्य मान स्टोर कर सकते हैं (करने के लिए उपयोगकर्ता बता बनाता आंशिक मान संग्रहीत कर सकते हैं तरह उनके बाहर अधिनियम को अधिक पारदर्शी)
  • वे क्योंकि वहाँ किसी भी
  • नहीं हैं सूचियों
  • विशेष मामलों स्टोर कर सकते हैं असंबंधित कोड पर हमला नहीं करेगा
  • , नियमित अभिव्यक्ति जानने को मान्य, खुश
  • आप उन्हें मजबूत प्रकार के लिए वास्तव में आसानी से

परिवर्तित कर सकते हैं हो सकता है मैंने पाया यह बहुत इस तरह थोड़ा चौखटे विकसित करने के लिए पुरस्कृत - यह एक शिक्षण अनुभव है और आप बाहर आया हूँ यूएक्स और मॉडलिंग की वास्तविकता के बारे में बहुत कुछ समझना। , (समयावधि)

    विशेष रूप से
    • दिनांक, समय, समय मुहर (मैं क्या दिनांक समय कहते हैं), काल:

      परीक्षण मामलों है कि मैं आपको पहले से निपटने के लिए सलाह देंगे के चार समूहों रहे हैं सुनिश्चित करें कि आप क्लाइंट के एक अलग सर्वर इलाके का परीक्षण करने का परीक्षण करें।

  • सूचियों - बहु-चयन विदेशी कुंजी आदि
  • शून्य मान
  • अमान्य इनपुट - यह आम तौर पर मूल मूल्य

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

अंत में, यदि आप कुछ भी करने के बिना इस तरह के सामान के ठोस कार्यान्वयन चाहते हैं, तो DataSets पर विचार करें - पुराने स्कूल मुझे पता है - वे सभी अद्भुत चीजें करते हैं जिन्हें आप उम्मीद नहीं करेंगे लेकिन आपको आरटीएफएम करना होगा ।

उस विचार का मुख्य पतन यह होगा कि यह WPF डेटा बाध्यकारी के साथ संगत नहीं है - हालांकि मेरा अनुभव यह है कि वास्तविकता WPF डेटा बाध्यकारी के साथ संगत नहीं है।

मुझे आशा है कि मैं अपने इरादों को सही ढंग से व्याख्या की - सौभाग्य किसी भी तरह :)

2

दुर्भाग्य से, वहाँ C<T> और उदाहरण के लिए C<string> के बीच कोई विरासत संबंध। हालांकि, आप एक सामान्य गैर-जेनेरिक वर्ग से प्राप्त कर सकते हैं और इसके अतिरिक्त एक सामान्य इंटरफ़ेस लागू कर सकते हैं। यहां मैं स्पष्ट इंटरफ़ेस कार्यान्वयन का उपयोग ऑब्जेक्ट के रूप में टाइप की गई वैल्यू प्रॉपर्टी घोषित करने में सक्षम होने के साथ-साथ एक विशेष रूप से टाइप की गई वैल्यू प्रॉपर्टी का उपयोग करने में सक्षम हूं। मान केवल पढ़ने के लिए हैं और केवल एक टाइप किए गए कन्स्ट्रक्टर पैरामीटर के माध्यम से असाइन किए जा सकते हैं। मेरा निर्माण सही नहीं है, लेकिन सुरक्षित टाइप करें और प्रतिबिंब का उपयोग नहीं करता है।

var list = new List<DataField>(); 
switch (fieldType) { 
    case "string": 
     list.Add(new StringDataField("Item", "Apple")); 
     break; 
    case "int": 
     list.Add(new IntDataField("Count", 12)); 
     break; 
} 

इंटरफेस के माध्यम से प्रवेश दृढ़ता से टाइप किया क्षेत्र:

public interface IValue<T> 
{ 
    T Value { get; } 
} 

public abstract class DataField 
{ 
    public DataField(string name, object value) 
    { 
     Name = name; 
     Value = value; 
    } 
    public string Name { get; private set; } 
    public object Value { get; private set; } 
} 

public class StringDataField : DataField, IValue<string> 
{ 
    public StringDataField(string name, string value) 
     : base(name, value) 
    { 
    } 

    string IValue<string>.Value 
    { 
     get { return (string)Value; } 
    } 
} 

public class IntDataField : DataField, IValue<int> 
{ 
    public IntDataField(string name, int value) 
     : base(name, value) 
    { 
    } 

    int IValue<int>.Value 
    { 
     get { return (int)Value; } 
    } 
} 

सूची तो सामान्य पैरामीटर के रूप में सार आधार वर्ग DataField साथ घोषित किया जा सकता

public void ProcessDataField(DataField field) 
{ 
    var stringField = field as IValue<string>; 
    if (stringField != null) { 
     string s = stringField.Value; 
    } 
} 
2

जबकि अन्य प्रश्न ज्यादातर ने आपके एक्सएमएल तत्वों को जेनेरिक क्लास इंस्टेंस में बदलने के लिए एक सुरुचिपूर्ण समाधान का प्रस्ताव दिया है, मैं मॉडल के दृष्टिकोण को लेने के परिणामों से निपटने जा रहा हूं। डेटाफिल्ड क्लास डेटाफिल्ड < [एक्सएमएल एलिमेंट की विशेषता में परिभाषित प्रकार]> जैसी जेनेरिक के रूप में।

सूची में अपना डेटाफ़िल्फ़ उदाहरण चुनने के बाद आप इन फ़ील्ड का उपयोग करना चाहते हैं। उसका बहुरूपता खेल में आता है! आप अपने डेटाफिल्ड्स को एक समान तरीके से एक इलाज करना चाहते हैं। जेनरिक का उपयोग करने वाले समाधान अक्सर अजीब स्विच/अगर नंगा नाचते हैं क्योंकि सामान्य प्रकार के आधार पर व्यवहार को जोड़ने के लिए कोई आसान तरीका नहीं है C#।

आप इस तरह कोड देखा हो सकता है

var list = new List<DataField>() 
{ 
    new DataField<int>() {Name = "int", Value = 2}, 
    new DataField<string>() {Name = "string", Value = "stringValue"}, 
    new DataField<float>() {Name = "string", Value = 2f}, 
}; 

var sum = 0.0; 

foreach (var dataField in list) 
{ 
    if (dataField.GetType().IsGenericType) 
    { 
     if (dataField.GetType().GetGenericArguments()[0] == typeof(int)) 
     { 
      sum += ((DataField<int>) dataField).Value; 
     } 
     else if (dataField.GetType().GetGenericArguments()[0] == typeof(float)) 
     { 
      sum += ((DataField<float>)dataField).Value; 
     } 
     // .. 
    } 
} 

इस कोड को एक पूरी गड़बड़ है (मैं सभी संख्यात्मक DataField उदाहरणों की राशि की गणना करने के कोशिश कर रहा हूँ)!

public class DataField<T> : DataField 
{ 
    public T Value { get; set; } 
    public override double Sum(double sum) 
    { 
     if (typeof(T) == typeof(int)) 
     { 
      return sum + (int)Value; // Cannot really cast here! 
     } 
     else if (typeof(T) == typeof(float)) 
     { 
      return sum + (float)Value; // Cannot really cast here! 
     } 
     // ... 

     return sum; 
    } 
} 

आप कल्पना कर सकते हैं कि:

की यह करने के लिए अपने सामान्य प्रकार DataField साथ बहुरूपी कार्यान्वयन की कोशिश जाने के लिए और योग कुछ विधि जोड़ने पुराने स्वीकार करता है कि कुछ और (संभवतः संशोधित) नई योग देता है चलो आपका पुनरावृत्ति कोड अब बहुत स्पष्ट हो गया है लेकिन आपके पास अभी भी यह अजीब स्विच/अगर आपके कोड में कथन है। और यहां बिंदु आता है: जेनेरिक आपकी मदद नहीं करते हैं यह गलत जगह पर गलत उपकरण है। जेनिक्स को संभावित असुरक्षित कास्ट ऑपरेशंस से बचने के लिए समय-समय पर सुरक्षा संकलित करने के लिए सी # में डिज़ाइन किया गया है। वे अतिरिक्त कोड पठनीयता के लिए जोड़ लेकिन उस मामले यहाँ नहीं है :)

के बहुरूपी समाधान पर एक नज़र डालें:

public abstract class DataField 
{ 
    public string Name { get; set; } 
    public object Value { get; set; } 
    public abstract double Sum(double sum); 
} 

public class IntDataField : DataField 
{ 
    public override double Sum(double sum) 
    { 
     return (int)Value + sum; 
    } 
} 

public class FloatDataField : DataField 
{ 
    public override double Sum(double sum) 
    { 
     return (float)Value + sum; 
    } 
} 

मुझे लगता है कि आप कल्पना करने के लिए कितना करने के लिए कहते हैं बहुत ज्यादा कल्पना की जरूरत नहीं होगी आपकी कोड की पठनीयता/गुणवत्ता।

अंतिम बिंदु इन कक्षाओं के उदाहरण बनाने का तरीका है।

Activator.CreateInstance("assemblyName", typeName); 

लघु संस्करण:

जेनेरिक्स आपकी समस्या के लिए उपयुक्त तरीका है क्योंकि यह DataField उदाहरणों में से निपटने के लिए मूल्य जोड़ने वाला नहीं नहीं है, बस कुछ सम्मेलन TypeName + "DataField" और उत्प्रेरक का उपयोग करके । बहुरूपी दृष्टिकोण के साथ आप DataField के उदाहरण के साथ आसानी से काम कर सकते हैं!