c# - Templated प्रकार के सी#जेनेरिक नए() के लिए तर्क पास




.net generics (9)

.NET 3.5 में और आप सक्रियकर्ता वर्ग का उपयोग करने के बाद:

(T)Activator.CreateInstance(typeof(T), args)

मैं सूची में जोड़ते समय अपने कन्स्ट्रक्टर के माध्यम से टाइप टी की एक नई वस्तु बनाने की कोशिश कर रहा हूं।

मुझे संकलन त्रुटि मिल रही है: त्रुटि संदेश है:

'टी': एक चर के उदाहरण बनाते समय तर्क प्रदान नहीं कर सकता है

लेकिन मेरे वर्गों में एक रचनाकार तर्क है! मै इसे काम मे कैसे ले सकता हूँ?

public static string GetAllItems<T>(...) where T : new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
       tabListItems.Add(new T(listItem)); // error here.
   } 
   ...
}

आपको यह जोड़ने की जरूरत है कि टी: नया () कंपाइलर को यह बताने के लिए कि टी को डिफॉल्ट कन्स्ट्रक्टर प्रदान करने की गारंटी है।

public static string GetAllItems<T>(...) where T: new()

चूंकि कोई भी 'प्रतिबिंब' उत्तर पोस्ट करने के लिए परेशान नहीं है (जिसे मैं व्यक्तिगत रूप से सोचता हूं वह सबसे अच्छा जवाब है), यहां जाता है:

public static string GetAllItems<T>(...) where T : new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
       Type classType = typeof(T);
       ConstructorInfo classConstructor = classType.GetConstructor(new Type[] { listItem.GetType() });
       T classInstance = (T)classConstructor.Invoke(new object[] { listItem });

       tabListItems.Add(classInstance);
   } 
   ...
}

संपादित करें: यह उत्तर .NET 3.5 के सक्रियकर्ता के कारण बहिष्कृत है। क्रिएटइंस्टेंस, हालांकि यह अभी भी पुराने .NET संस्करणों में उपयोगी है।


बहुत पुराना सवाल, लेकिन नया जवाब ;-)

अभिव्यक्ति ट्री संस्करण : (मुझे लगता है कि उपवास और साफ समाधान)

वेली तंबुनन की तरह, "हम ऑब्जेक्ट बनाने के लिए अभिव्यक्ति वृक्ष का भी उपयोग कर सकते हैं"

यह दिए गए प्रकार / पैरामीटर के लिए 'कन्स्ट्रक्टर' (फ़ंक्शन) उत्पन्न करेगा। यह एक प्रतिनिधि देता है और पैरामीटर प्रकार वस्तुओं की सरणी के रूप में स्वीकार करता है।

यह रहा:

// this delegate is just, so you don't have to pass an object array. _(params)_
public delegate object ConstructorDelegate(params object[] args);

public static ConstructorDelegate CreateConstructor(Type type, params Type[] parameters)
{
    // Get the constructor info for these parameters
    var constructorInfo = type.GetConstructor(parameters);

    // define a object[] parameter
    var paramExpr = Expression.Parameter(typeof(Object[]));

    // To feed the constructor with the right parameters, we need to generate an array 
    // of parameters that will be read from the initialize object array argument.
    var constructorParameters = parameters.Select((paramType, index) =>
        // convert the object[index] to the right constructor parameter type.
        Expression.Convert(
            // read a value from the object[index]
            Expression.ArrayAccess(
                paramExpr,
                Expression.Constant(index)),
            paramType)).ToArray();

    // just call the constructor.
    var body = Expression.New(constructorInfo, constructorParameters);

    var constructor = Expression.Lambda<ConstructorDelegate>(body, paramExpr);
    return constructor.Compile();
}

उदाहरण MyClass:

public class MyClass
{
    public int TestInt { get; private set; }
    public string TestString { get; private set; }

    public MyClass(int testInt, string testString)
    {
        TestInt = testInt;
        TestString = testString;
    }
}

उपयोग:

// you should cache this 'constructor'
var myConstructor = CreateConstructor(typeof(MyClass), typeof(int), typeof(string));

// Call the `myConstructor` fucntion to create a new instance.
var myObject = myConstructor(10, "test message");

एक और उदाहरण: प्रकारों को सरणी के रूप में पास करना

var type = typeof(MyClass);
var args = new Type[] { typeof(int), typeof(string) };

// you should cache this 'constructor'
var myConstructor = CreateConstructor(type, args);

// Call the `myConstructor` fucntion to create a new instance.
var myObject = myConstructor(10, "test message");

अभिव्यक्ति का डीबग व्यू

.Lambda #Lambda1<TestExpressionConstructor.MainWindow+ConstructorDelegate>(System.Object[] $var1) {
    .New TestExpressionConstructor.MainWindow+MyClass(
        (System.Int32)$var1[0],
        (System.String)$var1[1])
}

यह जेनरेट किए गए कोड के बराबर है:

public object myConstructor(object[] var1)
{
    return new MyClass(
        (System.Int32)var1[0],
        (System.String)var1[1]);
}

छोटा नकारात्मक

ऑब्जेक्ट सरणी की तरह पारित होने पर सभी वैल्यूएटिप्स पैरामीटर बॉक्स किए जाते हैं।

सरल प्रदर्शन परीक्षण:

private void TestActivator()
{
    Stopwatch sw = Stopwatch.StartNew();
    for (int i = 0; i < 1024 * 1024 * 10; i++)
    {
        var myObject = Activator.CreateInstance(typeof(MyClass), 10, "test message");
    }
    sw.Stop();
    Trace.WriteLine("Activator: " + sw.Elapsed);
}

private void TestReflection()
{
    var constructorInfo = typeof(MyClass).GetConstructor(new[] { typeof(int), typeof(string) });

    Stopwatch sw = Stopwatch.StartNew();
    for (int i = 0; i < 1024 * 1024 * 10; i++)
    {
        var myObject = constructorInfo.Invoke(new object[] { 10, "test message" });
    }

    sw.Stop();
    Trace.WriteLine("Reflection: " + sw.Elapsed);
}

private void TestExpression()
{
    var myConstructor = CreateConstructor(typeof(MyClass), typeof(int), typeof(string));

    Stopwatch sw = Stopwatch.StartNew();

    for (int i = 0; i < 1024 * 1024 * 10; i++)
    {
        var myObject = myConstructor(10, "test message");
    }

    sw.Stop();
    Trace.WriteLine("Expression: " + sw.Elapsed);
}

TestActivator();
TestReflection();
TestExpression();

परिणाम:

Activator: 00:00:13.8210732
Reflection: 00:00:05.2986945
Expression: 00:00:00.6681696

Expressions का उपयोग करने के बजाय Expressions का उपयोग +/- 8 बार तेजी से ConstructorInfo इंफो को आमंत्रित करने और +/- 20 गुना तेज है


मैं कभी-कभी एक ऐसे दृष्टिकोण का उपयोग करता हूं जो संपत्ति इंजेक्शन का उपयोग करके उत्तरों के समान होता है, लेकिन कोड क्लीनर रखता है। गुणों के एक सेट के साथ बेस क्लास / इंटरफ़ेस रखने के बजाय, इसमें केवल एक (आभासी) प्रारंभ () - विधि है जो "गरीब व्यक्ति के निर्माता" के रूप में कार्य करती है। फिर आप प्रत्येक वर्ग को अपने स्वयं के प्रारंभिकरण को एक कन्स्ट्रक्टर के रूप में संभालने दे सकते हैं, जो विरासत श्रृंखलाओं को संभालने का एक ठोस तरीका भी जोड़ता है।

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

उदाहरण जो प्रारंभिकरण के लिए एक सामान्य शब्दकोश का उपयोग करता है:

void Main()
{
    var values = new Dictionary<string, int> { { "BaseValue", 1 }, { "DerivedValue", 2 } };

    Console.WriteLine(CreateObject<Base>(values).ToString());

    Console.WriteLine(CreateObject<Derived>(values).ToString());
}

public T CreateObject<T>(IDictionary<string, int> values)
    where T : Base, new()
{
    var obj = new T();
    obj.Initialize(values);
    return obj;
}

public class Base
{
    public int BaseValue { get; set; }

    public virtual void Initialize(IDictionary<string, int> values)
    {
        BaseValue = values["BaseValue"];
    }

    public override string ToString()
    {
        return "BaseValue = " + BaseValue;
    }
}

public class Derived : Base
{
    public int DerivedValue { get; set; }

    public override void Initialize(IDictionary<string, int> values)
    {
        base.Initialize(values);
        DerivedValue = values["DerivedValue"];
    }

    public override string ToString()
    {       
        return base.ToString() + ", DerivedValue = " + DerivedValue;
    }
}

मैंने पाया कि मुझे त्रुटि हो रही है "प्रकार पैरामीटर टी का उदाहरण बनाते समय तर्क प्रदान नहीं कर सकता" इसलिए मुझे ऐसा करने की आवश्यकता थी:

var x = Activator.CreateInstance(typeof(T), args) as T;

यदि उस कक्षा तक पहुंच है जिसका आप उपयोग करने जा रहे हैं, तो आप इस दृष्टिकोण का उपयोग कर सकते हैं जिसका मैंने उपयोग किया था।

एक वैकल्पिक निर्माता बनाने वाला इंटरफ़ेस बनाएं:

public interface ICreatable1Param
{
    void PopulateInstance(object Param);
}

अपने वर्ग को एक खाली निर्माता के साथ बनाएं और इस विधि को लागू करें:

public class MyClass : ICreatable1Param
{
    public MyClass() { //do something or nothing }
    public void PopulateInstance (object Param)
    {
        //populate the class here
    }
}

अब अपने सामान्य तरीकों का उपयोग करें:

public void MyMethod<T>(...) where T : ICreatable1Param, new()
{
    //do stuff
    T newT = new T();
    T.PopulateInstance(Param);
}

यह आपकी स्थिति में काम नहीं करेगा। आप केवल उस बाधा को निर्दिष्ट कर सकते हैं जिसमें उसका खाली कन्स्ट्रक्टर है:

public static string GetAllItems<T>(...) where T: new()

आप इस इंटरफ़ेस को परिभाषित करके संपत्ति इंजेक्शन का उपयोग कर सकते हैं:

public interface ITakesAListItem
{
   ListItem Item { set; }
}

फिर आप यह करने के लिए अपनी विधि बदल सकते हैं:

public static string GetAllItems<T>(...) where T : ITakesAListItem, new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
       tabListItems.Add(new T() { Item = listItem });
   } 
   ...
}

दूसरा विकल्प जेरेडपार द्वारा वर्णित Func विधि है।


ऑब्जेक्ट प्रारंभकर्ता

यदि पैरामीटर के साथ आपका कन्स्ट्रक्टर किसी संपत्ति को सेट करने के अलावा कुछ भी नहीं कर रहा है, तो आप इसे कन्स्ट्रक्टर (जिसे असंभव है, जैसा कि उल्लेख किया गया है) को कॉल करने के बजाए ऑब्जेक्ट प्रारंभकर्ता का उपयोग करके सी # 3 में बेहतर कर सकते हैं:

public static string GetAllItems<T>(...) where T : new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
       tabListItems.Add(new T() { YourPropertyName = listItem } ); // Now using object initializer
   } 
   ...
}

इसका उपयोग करके, आप हमेशा किसी भी कन्स्ट्रक्टर तर्क को डिफ़ॉल्ट (खाली) कन्स्ट्रक्टर में भी डाल सकते हैं।

Activator.CreateInstance ()

वैकल्पिक रूप से, आप Activator.CreateInstance() को कॉल कर सकते हैं:

public static string GetAllItems<T>(...) where T : new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection) 
   {
        object[] args = new object[] { listItem };
        tabListItems.Add((T)Activator.CreateInstance(typeof(T), args)); // Now using Activator.CreateInstance
   } 
   ...
}

ध्यान दें कि एक्टिवेटर। क्रिएटइंस्टेंस में कुछ प्रदर्शन ओवरहेड हो सकते हैं, यदि आप निष्पादन गति सर्वोच्च प्राथमिकता है और दूसरा विकल्प आपके लिए बनाए रखा जा सकता है तो आप इससे बच सकते हैं।







new-operator