सामान्य प्रकार का उदाहरण बनाएं जिसके निर्माता को एक पैरामीटर की आवश्यकता होती है?


230

अगर BaseFruitएक कंस्ट्रक्टर है जो एक को स्वीकार करता है int weight, तो क्या मैं इस तरह से जेनेरिक विधि में फल के एक टुकड़े को तुरंत हटा सकता हूं?

public void AddFruit<T>()where T: BaseFruit{
    BaseFruit fruit = new T(weight); /*new Apple(150);*/
    fruit.Enlist(fruitManager);
}

टिप्पणियों के पीछे एक उदाहरण जोड़ा जाता है। ऐसा लगता है कि मैं केवल ऐसा कर सकता हूं अगर मैं BaseFruitएक पैरामीटर रहित निर्माता देता हूं और फिर सदस्य चर के माध्यम से सब कुछ भर देता हूं । मेरे वास्तविक कोड में (फल के बारे में नहीं) यह अव्यावहारिक है।

-Update-
तो ऐसा लगता है कि यह किसी भी तरह से बाधाओं द्वारा हल नहीं किया जा सकता है। जवाब से तीन उम्मीदवार समाधान कर रहे हैं:

  • फैक्टरी पैटर्न
  • प्रतिबिंब
  • उत्प्रेरक

मुझे लगता है कि प्रतिबिंब कम से कम एक साफ है, लेकिन मैं अन्य दो के बीच तय नहीं कर सकता।


1
BTW: आज मैं शायद पसंद के IoC पुस्तकालय के साथ इसे हल करेंगे।
बोरिस

प्रतिबिंब और उत्प्रेरक वास्तव में निकटता से संबंधित हैं।
रोब वर्म्यूलेन

जवाबों:


335

इसके अतिरिक्त एक सरल उदाहरण:

return (T)Activator.CreateInstance(typeof(T), new object[] { weight });

ध्यान दें कि टी पर नए () बाधा का उपयोग केवल संकलन पैरामीटर को सार्वजनिक समय पर करने के लिए कंपाइलर चेक करने के लिए किया जाता है, टाइप बनाने के लिए उपयोग किया जाने वाला वास्तविक कोड एक्टीवेटर क्लास है।

आपको मौजूदा निर्माता के बारे में खुद को सुनिश्चित करना होगा, और इस तरह की आवश्यकता एक कोड गंध हो सकती है (या बल्कि कुछ और जिसे आपको वर्तमान संस्करण में सी # से बचने की कोशिश करनी चाहिए)।


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

3
यह ठीक काम किया। वहाँ भी एक CreateInstance <T> () प्रक्रिया है, लेकिन यह कुछ रासन के लिए मापदंडों के लिए एक अधिभार नहीं है ..
Boris 11:12

20
उपयोग करने की कोई आवश्यकता नहीं है new object[] { weight }CreateInstanceपरम के साथ घोषित किया जाता है public static object CreateInstance(Type type, params object[] args), इसलिए आप बस कर सकते हैं return (T) Activator.CreateInstance(typeof(T), weight);। यदि कई पैरामीटर हैं, तो उन्हें अलग-अलग तर्कों के रूप में पास करें। केवल तभी जब आपके पास पहले से ही निर्मित मानकों के अनुसार आपको इसे बदलने object[]और उसको पास करने के लिए परेशान करना चाहिए CreateInstance
एरिक

2
इसमें प्रदर्शन के मुद्दे होंगे जो मैंने पढ़े हैं। इसके बजाय संकलित लैम्ब्डा का उपयोग करें। vagifabilov.wordpress.com/2010/04/02/…
डेविड

1
@RobVermeulen - मुझे लगता है कि प्रत्येक फल वर्ग पर एक स्थिर संपत्ति की तरह कुछ है, जिसमें एक Funcनया उदाहरण है। मान लीजिए कि Appleकंस्ट्रक्टर का उपयोग होता है new Apple(wgt)। फिर Appleइस परिभाषा को वर्ग में जोड़ें : static Func<float, Fruit> CreateOne { get; } = (wgt) => new Apple(wgt);कारखाने में public static Fruit CreateFruitGiven(float weight, Func<float, Fruit> createOne) { return createOne(weight); } उपयोग को परिभाषित करें : Factory.CreateFruit(57.3f, Apple.CreateOne);- जो बनाता है और एक Appleके साथ रिटर्न देता है weight=57.3f
टूलमेकरसैट

92

आप किसी भी पैरामीटर किए गए निर्माता का उपयोग नहीं कर सकते। अगर आपके पास "where T : new() " बाधा है ।

यह एक दर्द है, लेकिन ऐसा जीवन है :(

यह उन चीजों में से एक है जिसे मैं "स्थिर इंटरफेस" के साथ संबोधित करना चाहता हूं । तब आप स्थैतिक विधियों, ऑपरेटरों और कंस्ट्रक्टरों को शामिल करने के लिए T को विवश करने में सक्षम होंगे, और फिर उन्हें कॉल करेंगे।


2
कम से कम आप इस तरह की बाधाओं को कर सकते हैं - जावा हमेशा मुझे निराश करता है।
मार्सेल जैकवर्थ

@JonSkeet: अगर मैंने एपीआई को .NET जेनेरिक के साथ VB6.0 में बुलाया जाए.. तो क्या यह अभी भी काम करने योग्य है?
रॉय ली

@Roylee: मुझे कोई पता नहीं है, लेकिन मुझे संदेह नहीं है।
जॉन स्कीट

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

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

61

हाँ; अपने को बदलो जहां होना है:

where T:BaseFruit, new()

हालांकि, यह केवल पैरामीटर रहित कंस्ट्रक्टर के साथ काम करता है । आपको अपनी संपत्ति सेट करने के कुछ अन्य साधन होने चाहिए (संपत्ति को स्वयं या कुछ इसी तरह सेट करना)।


यदि निर्माता के पास पैरामीटर नहीं हैं तो यह मेरे लिए सुरक्षित लगता है।
सदा

तुमने मेरी जान बचाई है। मैं T को क्लास और नए () कीवर्ड तक सीमित करने का प्रबंधन नहीं कर सका।
जीनोटाइपेक

28

सबसे सरल उपाय Activator.CreateInstance<T>()


1
सुझाव के लिए धन्यवाद, मुझे वह मिल गया जहां मुझे होना चाहिए। यद्यपि यह आपको एक पैरामीटर निर्मित निर्माता का उपयोग करने की अनुमति नहीं देता है। लेकिन आप गैर-सामान्य संस्करण का उपयोग कर सकते हैं: Activator.CreateInstance (टाइपोफ़ (T), नई ऑब्जेक्ट [] {...}) जहां ऑब्जेक्ट सरणी में कंस्ट्रक्टर के लिए तर्क होते हैं।
रॉब वर्म्यूलेन

19

जैसा कि जॉन ने बताया कि यह एक गैर-पैरामीटर निर्माणकर्ता को विवश करने के लिए जीवन है। हालांकि एक अलग समाधान एक कारखाने पैटर्न का उपयोग करना है। यह आसानी से विवश है

interface IFruitFactory<T> where T : BaseFruit {
  T Create(int weight);
}

public void AddFruit<T>( IFruitFactory<T> factory ) where T: BaseFruit {    
  BaseFruit fruit = factory.Create(weight); /*new Apple(150);*/    
  fruit.Enlist(fruitManager);
}

फिर भी एक अन्य विकल्प एक कार्यात्मक दृष्टिकोण का उपयोग करना है। फैक्ट्री विधि में पास।

public void AddFruit<T>(Func<int,T> factoryDel) where T : BaseFruit { 
  BaseFruit fruit = factoryDel(weight); /* new Apple(150); */
  fruit.Enlist(fruitManager);
}

2
अच्छा सुझाव - हालांकि अगर आप सावधान नहीं हैं तो आप जावा डोम एपीआई के नरक में, कारखानों के साथ प्रचुर मात्रा में समाप्त कर सकते हैं :(
जॉन स्कीट

हां, यह एक समाधान है जो मैं खुद को छुपा रहा था। लेकिन मैं अड़चन की रेखा में कुछ के लिए उम्मीद कर रहा था। तब नहीं लगता ..
बोरिस ने 11

@boris, दुर्भाग्यवश आप जिस बाधा भाषा को ढूंढ रहे हैं, वह इस समय मौजूद नहीं है
JaredPar

11

आप प्रतिबिंब का उपयोग करके कर सकते हैं:

public void AddFruit<T>()where T: BaseFruit
{
  ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { typeof(int) });
  if (constructor == null)
  {
    throw new InvalidOperationException("Type " + typeof(T).Name + " does not contain an appropriate constructor");
  }
  BaseFruit fruit = constructor.Invoke(new object[] { (int)150 }) as BaseFruit;
  fruit.Enlist(fruitManager);
}

संपादित करें: जोड़ा गया कंस्ट्रक्टर == अशक्त चेक।

संपादित करें: कैश का उपयोग करके एक तेज़ संस्करण:

public void AddFruit<T>()where T: BaseFruit
{
  var constructor = FruitCompany<T>.constructor;
  if (constructor == null)
  {
    throw new InvalidOperationException("Type " + typeof(T).Name + " does not contain an appropriate constructor");
  }
  var fruit = constructor.Invoke(new object[] { (int)150 }) as BaseFruit;
  fruit.Enlist(fruitManager);
}
private static class FruitCompany<T>
{
  public static readonly ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { typeof(int) });
}

हालांकि मुझे प्रतिबिंब का ओवरहेड पसंद नहीं है, जैसा कि अन्य लोगों ने समझाया है, यह सिर्फ वर्तमान में है। यह देखते हुए कि इस निर्माता को बहुत अधिक नहीं कहा जाएगा, मैं इसके साथ जा सकता हूं। या कारखाना है। अभी तक पता नहीं है।
बोरिस

यह वर्तमान में मेरा पसंदीदा दृष्टिकोण है क्योंकि यह आह्वान पक्ष में अधिक जटिलता नहीं जोड़ता है।
रोब वर्म्यूलेन

लेकिन अब मैंने एक्टीवेटर सुझाव के बारे में पढ़ा है, जिसमें उपरोक्त प्रतिबिंब समाधान के समान ही उदासीनता है, लेकिन कोड की कम लाइनों के साथ :) मैं एक्टिविटर विकल्प के लिए जाने वाला हूं।
रोब वर्म्यूलेन

1

User1471935 के सुझाव के अतिरिक्त:

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

T instance = Activator.CreateInstance(typeof(T), new object[] {...}) 

वस्तुओं की सूची वे पैरामीटर हैं जिनकी आप आपूर्ति करना चाहते हैं। Microsoft के अनुसार :

CreateInstance [...] कंस्ट्रक्टर का उपयोग करके निर्दिष्ट प्रकार का एक उदाहरण बनाता है जो निर्दिष्ट मापदंडों से सबसे अच्छा मेल खाता है।

CreateInstance ( CreateInstance<T>()) का एक सामान्य संस्करण भी है, लेकिन वह भी आपको कंस्ट्रक्टर मापदंडों की आपूर्ति करने की अनुमति नहीं देता है।


1

मैंने यह तरीका बनाया है:

public static V ConvertParentObjToChildObj<T,V> (T obj) where V : new()
{
    Type typeT = typeof(T);
    PropertyInfo[] propertiesT = typeT.GetProperties();
    V newV = new V();
    foreach (var propT in propertiesT)
    {
        var nomePropT = propT.Name;
        var valuePropT = propT.GetValue(obj, null);

        Type typeV = typeof(V);
        PropertyInfo[] propertiesV = typeV.GetProperties();
        foreach (var propV in propertiesV)
        {
            var nomePropV = propV.Name;
            if(nomePropT == nomePropV)
            {
                propV.SetValue(newV, valuePropT);
                break;
            }
        }
    }
    return newV;
}

मैं इसे इस तरह से उपयोग करता हूं:

public class A 
{
    public int PROP1 {get; set;}
}

public class B : A
{
    public int PROP2 {get; set;}
}

कोड:

A instanceA = new A();
instanceA.PROP1 = 1;

B instanceB = new B();
instanceB = ConvertParentObjToChildObj<A,B>(instanceA);

0

हाल ही में मैं एक बहुत ही ऐसी ही समस्या के बारे में आया। बस हम आपके समाधान को आप सभी के साथ साझा करना चाहते थे। मैं चाहता था कि मैं Car<CarA>एक json ऑब्जेक्ट से एक उदाहरण का उपयोग करूं जिसमें एक एनम था:

Dictionary<MyEnum, Type> mapper = new Dictionary<MyEnum, Type>();

mapper.Add(1, typeof(CarA));
mapper.Add(2, typeof(BarB)); 

public class Car<T> where T : class
{       
    public T Detail { get; set; }
    public Car(T data)
    {
       Detail = data;
    }
}
public class CarA
{  
    public int PropA { get; set; }
    public CarA(){}
}
public class CarB
{
    public int PropB { get; set; }
    public CarB(){}
}

var jsonObj = {"Type":"1","PropA":"10"}
MyEnum t = GetTypeOfCar(jsonObj);
Type objectT = mapper[t]
Type genericType = typeof(Car<>);
Type carTypeWithGenerics = genericType.MakeGenericType(objectT);
Activator.CreateInstance(carTypeWithGenerics , new Object[] { JsonConvert.DeserializeObject(jsonObj, objectT) });

-2

यह अभी भी संभव है, उच्च प्रदर्शन के साथ, निम्न कार्य करके:

    //
    public List<R> GetAllItems<R>() where R : IBaseRO, new() {
        var list = new List<R>();
        using ( var wl = new ReaderLock<T>( this ) ) {
            foreach ( var bo in this.items ) {
                T t = bo.Value.Data as T;
                R r = new R();
                r.Initialize( t );
                list.Add( r );
            }
        }
        return list;
    }

तथा

    //
///<summary>Base class for read-only objects</summary>
public partial interface IBaseRO  {
    void Initialize( IDTO dto );
    void Initialize( object value );
}

तब संबंधित वर्गों को इस इंटरफ़ेस से व्युत्पन्न होना चाहिए और तदनुसार प्रारंभ करना होगा। कृपया ध्यान दें, कि मेरे मामले में, यह कोड आसपास के वर्ग का हिस्सा है, जिसके पास पहले से ही सामान्य पैरामीटर के रूप में <T> है। मेरे मामले में आर, केवल पढ़ने के लिए एक वर्ग है। IMO, प्रारंभ की सार्वजनिक उपलब्धता () कार्यों का अपरिवर्तनीयता पर कोई नकारात्मक प्रभाव नहीं पड़ता है। इस वर्ग का उपयोगकर्ता किसी अन्य ऑब्जेक्ट को डाल सकता है, लेकिन यह अंतर्निहित संग्रह को संशोधित नहीं करेगा।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.