การส่งผ่านอาร์กิวเมนต์ไปยัง C # generic ใหม่ () ประเภท templated


409

ฉันกำลังพยายามสร้างวัตถุใหม่ของประเภท T ผ่านตัวสร้างเมื่อเพิ่มลงในรายการ

ฉันพบข้อผิดพลาดในการคอมไพล์: ข้อความแสดงข้อผิดพลาดคือ:

'T': ไม่สามารถให้ข้อโต้แย้งเมื่อสร้างอินสแตนซ์ของตัวแปร

แต่คลาสของฉันมีอาร์กิวเมนต์ตัวสร้าง! ฉันจะทำงานนี้ได้อย่างไร

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

2
เป็นไปได้ที่ซ้ำกันของการสร้างตัวอย่างของประเภททั่วไป?
nawfal

2
ข้อเสนอเพื่อรับฟังก์ชั่นนี้เป็นภาษา: github.com/dotnet/roslyn/issues/2206
Ian Kemp

ในเอกสารไมโครซอฟท์ดูคอมไพเลอร์ข้อผิดพลาด CS0417
DavidRR

1
ข้อเสนอเพื่อรับฟังก์ชั่นนี้เป็นภาษาได้ถูกย้ายไปที่: github.com/dotnet/csharplang/issues/769
ลดกิจกรรม

คำตอบ:


410

ในการสร้างอินสแตนซ์ของประเภททั่วไปในฟังก์ชั่นคุณจะต้อง จำกัด ด้วยการตั้งค่าสถานะ "ใหม่"

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

อย่างไรก็ตามมันจะใช้ได้เฉพาะเมื่อคุณต้องการเรียกนวกรรมิกที่ไม่มีพารามิเตอร์ ไม่ใช่กรณีที่นี่ แต่คุณจะต้องระบุพารามิเตอร์อื่นที่อนุญาตให้สร้างวัตถุตามพารามิเตอร์ วิธีที่ง่ายที่สุดคือฟังก์ชั่น

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

จากนั้นคุณสามารถเรียกได้ว่าเป็นอย่างนั้น

GetAllItems<Foo>(..., l => new Foo(l));

วิธีนี้จะทำงานเมื่อเรียกภายในจากชั้นเรียนทั่วไป? ฉันโพสต์รหัสของฉันในคำตอบด้านล่าง ฉันไม่รู้จักคลาสที่เป็นรูปธรรมภายในเนื่องจากเป็นคลาสทั่วไป มีวิธีรอบนี้หรือไม่ ฉันไม่ต้องการที่จะใช้ข้อเสนอแนะอื่น ๆ ของการใช้ทรัพย์สิน initialiser ไวยากรณ์เป็นจะข้ามตรรกะที่ฉันมีในตัวสร้างที่
ChrisCa

เพิ่มรหัสของฉันไปยังอีกคำถามstackoverflow.com/questions/1682310/…
ChrisCa

21
นี่เป็นหนึ่งในข้อ จำกัด ที่น่ารำคาญที่สุดของ C # ฉันต้องการทำให้ชั้นเรียนของฉันไม่เปลี่ยนรูป: การมีผู้ตั้งค่าส่วนตัวเพียงอย่างเดียวจะทำให้ชั้นไม่สามารถอยู่ในสถานะที่ไม่ถูกต้องจากผลข้างเคียง ฉันชอบที่จะใช้ Func และแลมบ์ดา แต่ฉันรู้ว่ามันยังคงเป็นปัญหาในโลกธุรกิจเนื่องจากโปรแกรมเมอร์โดยทั่วไปยังไม่รู้จักแลมบ์ดาและสิ่งนี้ทำให้ห้องเรียนของคุณเข้าใจยากขึ้น
Tuomas Hietanen

1
ขอบคุณ ในกรณีของฉันฉันรู้ว่าคอนสตรัคของอาร์กิวเมนต์ (s) เมื่อฉันเรียกวิธีการที่ฉันเพียงต้องการที่จะได้รับรอบข้อ จำกัด ประเภทของพารามิเตอร์ว่ามันอาจจะไม่ได้ถูกสร้างขึ้นด้วยพารามิเตอร์ดังนั้นผมใช้thunk thunk เป็นพารามิเตอร์ทางเลือกสำหรับวิธีการและฉันใช้เฉพาะเมื่อมีให้: T result = thunk == null ? new T() : thunk(); ประโยชน์ของสิ่งนี้สำหรับฉันคือการรวมตรรกะของTการสร้างในที่เดียวมากกว่าบางครั้งการสร้างTภายในและนอกบางครั้งวิธี
Carl G

ฉันคิดว่าสิ่งเหล่านี้เป็นหนึ่งในสถานที่ที่ภาษา C # ตัดสินใจที่จะปฏิเสธโปรแกรมและหยุดพูดว่าใช่ตลอดเวลา! แม้ว่าวิธีการนี้เป็นวิธีการสร้างวัตถุที่น่าอึดอัดใจเล็กน้อย แต่ฉันต้องใช้มันในตอนนี้
AmirHossein Rezaei

331

ใน. Net 3.5 และหลังจากคุณสามารถใช้คลาส activator:

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

1
นอกจากนี้เรายังสามารถใช้ต้นไม้แสดงผลเพื่อสร้างวัตถุ
Welly Tambunan

4
args คืออะไร วัตถุ []?
Rodney P. Barbati

3
ใช่ args เป็นวัตถุ [] ซึ่งคุณระบุค่าที่จะมอบให้กับ Constructor ของ T: "new object [] {par1, par2}"
TechNyquist


3
คำเตือน: หากคุณมีคอนสตรัคเตอร์เฉพาะเพื่อเห็นแก่Activator.CreateInstanceสิ่งนี้มันจะดูเหมือนว่าคอนสตรัคเตอร์ของคุณไม่ได้ใช้เลยและบางคนอาจลอง "ล้าง" และลบมันออก (เพื่อทำให้เกิดข้อผิดพลาดรันไทม์ที่ บางเวลาแบบสุ่มในอนาคต) คุณอาจต้องการพิจารณาการเพิ่มฟังก์ชันดัมมี่ที่คุณใช้ตัวสร้างนี้เพื่อให้คุณได้รับข้อผิดพลาดในการคอมไพล์ถ้าคุณพยายามลบมัน
jrh

51

เนื่องจากไม่มีใครใส่ใจที่จะโพสต์คำตอบ 'สะท้อน' (ซึ่งโดยส่วนตัวแล้วฉันคิดว่าเป็นคำตอบที่ดีที่สุด) ต่อไปนี้:

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

แก้ไข: คำตอบนี้เลิกใช้แล้วเนื่องจาก Activator.CreateInstance ของ. NET 3.5 แต่ก็ยังมีประโยชน์ในเวอร์ชั่นเก่า NET


ความเข้าใจของฉันคือส่วนใหญ่ของประสิทธิภาพการทำงานในการรับ ConstructorInfo ในสถานที่แรก อย่าใช้คำของฉันโดยไม่ทำโปรไฟล์ หากเป็นเช่นนั้นการจัดเก็บ ConstructorInfo เพื่อนำมาใช้ซ้ำในภายหลังจะช่วยลดประสิทธิภาพการทำงานของอินสแตนซ์ซ้ำผ่านการสะท้อนกลับ
Kelsie

19
ฉันคิดว่าการขาดการตรวจสอบเวลาคอมไพล์เป็นสาเหตุของความกังวลมากกว่า
เดฟแวนเดน Eynde

1
@ James ฉันเห็นด้วยฉันรู้สึกประหลาดใจที่ไม่เห็นว่านี่เป็น "คำตอบ" ในความเป็นจริงฉันค้นหาคำถามนี้โดยคาดหวังว่าจะพบตัวอย่างง่าย ๆ ที่ดี (เช่นของคุณ) เนื่องจากมันใช้เวลานานมากตั้งแต่ฉันได้ไตร่ตรองมาแล้ว อย่างไรก็ตาม +1 จากฉัน แต่ +1 บน Activator ก็ตอบเช่นกัน ฉันดูว่า Activator กำลังทำอะไรและปรากฎว่าสิ่งที่สะท้อนออกมาได้ดีคืออะไร :)
Mike

การเรียก GetConstructor () มีราคาแพงดังนั้นจึงคุ้มค่าที่จะแคชก่อนการวนรอบ ด้วยวิธีนี้การเรียกใช้เฉพาะ Invoke () ภายในลูปนั้นจะเร็วกว่าการโทรทั้งสองอย่างหรือแม้กระทั่งการใช้ Activator.CreateInstance ()
Cosmin Rus

30

วัตถุเริ่มต้น

หากคอนสตรัคเตอร์ที่มีพารามิเตอร์ไม่ได้ทำอะไรนอกจากการตั้งค่าคุณสมบัติคุณสามารถทำได้ใน C # 3 หรือดีกว่าโดยใช้object initializerแทนการเรียกคอนสตรัคเตอร์ (ซึ่งเป็นไปไม่ได้ดังที่ได้กล่าวไว้แล้ว):

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

โปรดทราบว่า Activator.CreateInstance สามารถมีค่าใช้จ่ายในการปฏิบัติงานบางอย่างที่คุณอาจต้องการหลีกเลี่ยงหากความเร็วในการดำเนินการเป็นเรื่องสำคัญที่สุดและเป็นอีกทางเลือกหนึ่งที่คุณสามารถดูแลรักษาได้


สิ่งนี้จะป้องกันไม่ให้Tมันเป็นค่าคงที่ (เนื่องจากTมี> 0 หรือค่าที่ต้องการตอนนี้คุณสามารถสร้างอินสแตนซ์Tที่อยู่ในสถานะที่ไม่ถูกต้อง / ไม่สามารถใช้งานได้ยกเว้นในTบางกรณี
sara

20

คำถามเก่ามาก แต่คำตอบใหม่ ;-)

รุ่น ExpressionTree : (ฉันคิดว่าเร็วที่สุดและวิธีแก้ปัญหาที่สะอาดที่สุด)

เหมือนWelly Tambunanกล่าวว่า"เราสามารถใช้ต้นไม้แสดงผลเพื่อสร้างวัตถุ"

สิ่งนี้จะสร้าง 'ตัวสร้าง' (ฟังก์ชัน) สำหรับประเภท / พารามิเตอร์ที่กำหนด มันจะส่งกลับผู้รับมอบสิทธิ์และยอมรับประเภทพารามิเตอร์เป็นอาร์เรย์ของวัตถุ

นี่มันคือ:

// 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` function 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");

DebugView ของนิพจน์

.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+/- 8 เท่าConstructorInfoและ +/- เร็วขึ้น 20 เท่าเมื่อใช้งานActivator


คุณมีข้อมูลเชิงลึกเกี่ยวกับสิ่งที่ต้องทำถ้าคุณต้องการสร้าง MyClass <T> ด้วย MyClass public constructor (T data) ในกรณีนี้ Expression.Convert พ่นยกเว้นและถ้าผมใช้คลาสฐาน จำกัด ทั่วไปเพื่อแปลงไปแล้ว Expression.New พ่นเพราะข้อมูลสร้างเป็นชนิดทั่วไป
เมสัน

@ Mason (ใช้เวลาสักครู่ในการตอบ ;-)) var myConstructor = CreateConstructor(typeof(MyClass<int>), typeof(int));มันใช้งานได้ดี ฉันไม่รู้
Jeroen van Langen

19

สิ่งนี้จะไม่ทำงานในสถานการณ์ของคุณ คุณสามารถระบุข้อ จำกัด ที่มีตัวสร้างเปล่า:

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วิธีที่ JaredPar อธิบาย


นี่จะเลี่ยงผ่านตรรกะใด ๆ ที่อยู่ในคอนสตรัคเตอร์ที่รับอาร์กิวเมนต์แม้ว่าใช่ไหม? ฉันต้องการจะทำสิ่งที่ชอบวิธีการของ Jared แต่ฉันกำลังเรียกวิธีการภายในภายในชั้นเรียนดังนั้นไม่ทราบว่ารูปแบบที่เป็นรูปธรรมคือ ... hmmm
ChrisCa

3
ขวานี่เรียกตรรกะของคอนสตรัคค่าเริ่มต้น T () จากนั้นตั้งค่าคุณสมบัติ "ไอเท็ม" หากคุณพยายามที่จะเรียกใช้ตรรกะของนวกรรมิกที่ไม่ใช่ค่าเริ่มต้นสิ่งนี้จะไม่ช่วยคุณ
Scott Stafford

7

คุณต้องเพิ่มตำแหน่งที่ T: new () เพื่อให้คอมไพเลอร์ทราบว่า T รับประกันว่าจะให้ตัวสร้างเริ่มต้น

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

1
UPDATE: ข้อความแสดงข้อผิดพลาดที่ถูกต้องคือ: 'T': ไม่สามารถระบุอาร์กิวเมนต์เมื่อสร้างอินสแตนซ์ของตัวแปร
LB

นั่นเป็นเพราะคุณไม่ได้ใช้ตัวสร้างเปล่าคุณกำลังส่งอาร์กิวเมนต์ไปยังวัตถุ ไม่มีวิธีที่จะสามารถจัดการกับมันได้โดยไม่ต้องระบุว่าประเภททั่วไปมีพารามิเตอร์ใหม่ (วัตถุ)
ต่ำสุด

จากนั้นคุณจะต้อง: 1. ใช้การสะท้อน 2. ผ่านพารามิเตอร์ไปยังวิธีการเริ่มต้นแทนตัวสร้างซึ่งวิธีการเริ่มต้นเป็นของอินเทอร์เฟซที่ประเภทของคุณใช้และซึ่งรวมอยู่ในที่ T: ... การประกาศ ตัวเลือกที่ 1 คือผลกระทบต่ำสุดสำหรับส่วนที่เหลือของรหัสของคุณ แต่ตัวเลือก 2 ให้การตรวจสอบเวลาการคอมไพล์
Richard

อย่าใช้การสะท้อน! มีวิธีอื่นตามที่ระบุไว้ในคำตอบอื่น ๆ ที่ให้ผลเช่นเดียวกันกับคุณ
Garry Shutler

@Garry - ฉันเห็นด้วยว่าการสะท้อนนั้นไม่ได้เป็นวิธีการที่ดีที่สุด แต่ก็ช่วยให้คุณสามารถบรรลุสิ่งที่ต้องการด้วยการเปลี่ยนแปลงเล็กน้อยในส่วนที่เหลือของรหัสฐาน ที่กล่าวว่าฉันชอบวิธีการมอบหมายโรงงานจาก @JaredPar มาก
Richard

7

ถ้าคุณเพียงต้องการเริ่มต้นเขตข้อมูลสมาชิกหรือคุณสมบัติด้วยพารามิเตอร์ตัวสร้างใน C #> = 3 คุณสามารถทำได้ง่ายขึ้น:

public static string GetAllItems<T>(...) where T : InterfaceOrBaseClass, new() 
{ 
   ... 
   List<T> tabListItems = new List<T>(); 
   foreach (ListItem listItem in listCollection)  
   { 
       tabListItems.Add(new T{ BaseMemberItem = listItem }); // No error, BaseMemberItem owns to InterfaceOrBaseClass. 
   }  
   ... 
} 

นี่คือสิ่งเดียวกันกับแกร์รีชัตเลอร์กล่าว แต่ฉันต้องการใส่หมายเหตุเพิ่มเติม

แน่นอนคุณสามารถใช้เคล็ดลับคุณสมบัติเพื่อทำสิ่งต่างๆได้มากกว่าเพียงแค่การตั้งค่าฟิลด์ คุณสมบัติ "set ()" สามารถเรียกใช้การประมวลผลใด ๆ ที่จำเป็นในการตั้งค่าฟิลด์ที่เกี่ยวข้องและความต้องการอื่น ๆ สำหรับวัตถุนั้นเองรวมถึงการตรวจสอบเพื่อดูว่าการเริ่มต้นเต็มรูปแบบเกิดขึ้นก่อนที่วัตถุจะถูกใช้ ใช่มันเป็นวิธีแก้ปัญหาที่น่าเกลียด แต่มันเอาชนะข้อ จำกัด () ใหม่ของ M $ ได้

ฉันไม่สามารถรับประกันได้ว่าจะเป็นหลุมที่วางแผนไว้หรือมีผลข้างเคียงโดยไม่ได้ตั้งใจ แต่มันได้ผล

มันตลกมากที่คน MS เพิ่มคุณสมบัติใหม่ให้กับภาษาและดูเหมือนว่าจะไม่ทำการวิเคราะห์ผลข้างเคียงเต็มรูปแบบ สิ่งสามัญทั้งหมดเป็นหลักฐานที่ดีของสิ่งนี้ ...


1
จำเป็นต้องมีทั้งข้อ จำกัด InterfaceOrBaseClass ทำให้คอมไพเลอร์ตระหนักถึงฟิลด์ / คุณสมบัติ BaseMemberItem หากข้อ จำกัด "new ()" ถูกคอมเม้นต์มันจะทริกเกอร์ข้อผิดพลาด: ข้อผิดพลาด 6 ไม่สามารถสร้างอินสแตนซ์ของประเภทตัวแปร 'T' ได้เนื่องจากไม่มีข้อ จำกัด ใหม่ ()
fljx

สถานการณ์ที่ฉันพบไม่เหมือนกับคำถามที่ถูกถามที่นี่ แต่คำตอบนี้ทำให้ฉันได้ว่าฉันต้องไปที่ไหนและดูเหมือนว่าจะทำงานได้ดีมาก
RubyHaus

5
ทุกครั้งที่มีคนพูดถึง Microsoft ว่า "M $" ส่วนเล็ก ๆ ของจิตวิญญาณของฉันต้องทนทุกข์ทรมาน
Mathias Lykkegaard Lorenzen

6

ฉันพบว่าฉันได้รับข้อผิดพลาด "ไม่สามารถให้ข้อโต้แย้งเมื่อสร้างอินสแตนซ์ของพารามิเตอร์ประเภท T" ดังนั้นฉันต้องทำสิ่งนี้:

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

5

หากคุณมีสิทธิ์เข้าถึงชั้นเรียนที่คุณจะใช้คุณสามารถใช้วิธีการที่ฉันใช้

สร้างอินเทอร์เฟซที่มีผู้สร้างทางเลือก:

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 class MyClass : ICreatable1Param
{
    public WrappedClass WrappedInstance {get; private set; }
    public MyClass() { //do something or nothing }
    public void PopulateInstance (object Param)
    {
        WrappedInstance = new WrappedClass(Param);
    }
}

0

นี่คือสิ่งที่น่ารังเกียจและเมื่อฉันพูดว่าชนิดที่น่ารังเกียจฉันอาจหมายถึงน่ารังเกียจ แต่ถ้าคุณสามารถให้ประเภทพารามิเตอร์ของคุณด้วยตัวสร้างเปล่าแล้ว:

public static T GetTInstance<T>() where T: new()
{
    var constructorTypeSignature = new Type[] {typeof (object)};
    var constructorParameters = new object[] {"Create a T"};
    return (T) new T().GetType().GetConstructor(constructorTypeSignature).Invoke(constructorParameters);
}

จะช่วยให้คุณสามารถสร้างวัตถุจากประเภทพารามิเตอร์ด้วยอาร์กิวเมนต์ ในกรณีนี้ฉันสมมติว่าตัวสร้างที่ฉันต้องการมีอาร์กิวเมนต์ชนิดobjectเดียว เราสร้างตัวอย่างจำลองของ T โดยใช้ข้อ จำกัด ที่อนุญาตให้ตัวสร้างว่างแล้วใช้การสะท้อนเพื่อรับตัวสร้างอื่น


0

บางครั้งฉันใช้วิธีการที่คล้ายกับคำตอบโดยใช้การฉีดคุณสมบัติ แต่เก็บรหัสสะอาด แทนที่จะมีคลาสพื้นฐาน / อินเตอร์เฟสพร้อมชุดคุณสมบัติมันมีเพียง (เสมือน) Initialize () - วิธีการที่ทำหน้าที่เป็น "ตัวสร้างของคนจน" จากนั้นคุณสามารถปล่อยให้แต่ละคลาสจัดการมันเป็นค่าเริ่มต้นของตัวเองเช่นเดียวกับคอนสตรัคเตอร์ซึ่งจะเพิ่มวิธีการจัดการโซ่สืบทอด

หากมักจะพบว่าตัวเองอยู่ในสถานการณ์ที่ฉันต้องการให้แต่ละชั้นเรียนในห่วงโซ่เพื่อเริ่มต้นคุณสมบัติที่เป็นเอกลักษณ์จากนั้นเรียกวิธีการเริ่มต้น () ของผู้ปกครองซึ่งเป็นวิธีการเริ่มต้นคุณสมบัติที่เป็นเอกลักษณ์ของผู้ปกครองเป็นต้น สิ่งนี้มีประโยชน์อย่างยิ่งเมื่อมีคลาสที่แตกต่างกัน แต่มีลำดับชั้นที่คล้ายคลึงกันเช่นวัตถุธุรกิจที่แมปไปยัง / จาก DTO: s

ตัวอย่างที่ใช้พจนานุกรมทั่วไปสำหรับการเริ่มต้น:

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

0

หากคุณต้องการทั้งหมดคือการแปลงจาก ListItem เป็นชนิด T ของคุณคุณสามารถใช้การแปลงนี้ในคลาส T เป็นตัวดำเนินการแปลง

public class T
{
    public static implicit operator T(ListItem listItem) => /* ... */;
}

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

-4

ฉันเชื่อว่าคุณต้อง จำกัด T ด้วยคำสั่ง where เพื่ออนุญาตเฉพาะวัตถุที่มี Constructor ใหม่

ตอนนี้มันยอมรับทุกอย่างรวมถึงวัตถุที่ไม่มีมัน


1
คุณอาจต้องการเปลี่ยนคำตอบนี้เพราะสิ่งนี้ได้ถูกแก้ไขเป็นคำถามหลังจากที่คุณตอบซึ่งทำให้คำตอบนี้ไม่อยู่ในบริบท
shuttle87
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.