ใน C # วิธีการสร้างอินสแตนซ์ประเภททั่วไปที่ส่งผ่านภายในวิธีการ?


100

ฉันจะสร้างอินสแตนซ์ประเภท T ภายในInstantiateType<T>วิธีการด้านล่างได้อย่างไร

ฉันได้รับข้อผิดพลาด: 'T' เป็น 'พารามิเตอร์ประเภท' แต่ใช้เหมือน 'ตัวแปร' :

(เลื่อนลงสำหรับคำตอบที่อ้างอิง)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestGeneric33
{
    class Program
    {
        static void Main(string[] args)
        {
            Container container = new Container();
            Console.WriteLine(container.InstantiateType<Customer>("Jim", "Smith"));
            Console.WriteLine(container.InstantiateType<Employee>("Joe", "Thompson"));
            Console.ReadLine();
        }
    }

    public class Container
    {
        public T InstantiateType<T>(string firstName, string lastName) where T : IPerson
        {
            T obj = T();
            obj.FirstName(firstName);
            obj.LastName(lastName);
            return obj;
        }

    }

    public interface IPerson
    {
        string FirstName { get; set; }
        string LastName { get; set; }
    }

    public class Customer : IPerson
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Company { get; set; }
    }

    public class Employee : IPerson
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int EmployeeNumber { get; set; }
    }
}

คำตอบอ้างอิง:

ขอบคุณสำหรับความคิดเห็นทั้งหมดพวกเขาทำให้ฉันมาถูกทางนี่คือสิ่งที่ฉันต้องการทำ:

using System;

namespace TestGeneric33
{
    class Program
    {
        static void Main(string[] args)
        {
            Container container = new Container();
            Customer customer1 = container.InstantiateType<Customer>("Jim", "Smith");
            Employee employee1 = container.InstantiateType<Employee>("Joe", "Thompson");
            Console.WriteLine(PersonDisplayer.SimpleDisplay(customer1));
            Console.WriteLine(PersonDisplayer.SimpleDisplay(employee1));
            Console.ReadLine();
        }
    }

    public class Container
    {
        public T InstantiateType<T>(string firstName, string lastName) where T : IPerson, new()
        {
            T obj = new T();
            obj.FirstName = firstName;
            obj.LastName = lastName;
            return obj;
        }
    }

    public interface IPerson
    {
        string FirstName { get; set; }
        string LastName { get; set; }
    }

    public class PersonDisplayer
    {
        private IPerson _person;

        public PersonDisplayer(IPerson person)
        {
            _person = person;
        }

        public string SimpleDisplay()
        {
            return String.Format("{1}, {0}", _person.FirstName, _person.LastName);
        }

        public static string SimpleDisplay(IPerson person)
        {
            PersonDisplayer personDisplayer = new PersonDisplayer(person);
            return personDisplayer.SimpleDisplay();
        }
    }

    public class Customer : IPerson
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Company { get; set; }
    }

    public class Employee : IPerson
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int EmployeeNumber { get; set; }
    }
}

+1 เพื่อเปลี่ยนเป็นรูปแบบการออกแบบที่ดีขึ้น
Joel Coehoorn

+1 สำหรับรหัสที่พิมพ์อย่างประณีตเป็นสิ่งที่หายาก
nawfal

คำตอบ:


133

ประกาศวิธีการของคุณดังนี้:

public string InstantiateType<T>(string firstName, string lastName) 
              where T : IPerson, new()

สังเกตข้อ จำกัด เพิ่มเติมในตอนท้าย จากนั้นสร้างnewอินสแตนซ์ในเนื้อหาวิธีการ:

T obj = new T();    

4
ฉันเขียน C # มาหลายปีแล้วโดยมีการพิมพ์ผิดประเภททั่วไปอย่างหนักในสมัยของฉันและฉันไม่เคยรู้เลยว่าคุณสามารถกำหนดข้อ จำกัด เช่นนี้เพื่อสร้างอินสแตนซ์ประเภททั่วไปได้ ขอบคุณมาก!
Nicolas Martel

สวยมาก !!
Sotiris Zegiannis

จะเกิดอะไรขึ้นถ้าไม่ระบุประเภทเป็นไปได้ไหม?
jj

31

สองวิธี

โดยไม่ต้องระบุประเภทจะต้องมีตัวสร้าง:

T obj = default(T); //which will produce null for reference types

ด้วยตัวสร้าง:

T obj = new T();

แต่สิ่งนี้ต้องการอนุประโยค:

where T : new()

1
อันแรกจะกำหนด null แทนที่จะสร้างอินสแตนซ์สำหรับประเภทการอ้างอิง
Joel Coehoorn

1
ใช่. คุณต้องใช้การสะท้อนเพื่อสร้างชนิดโดยไม่มีตัวสร้างเริ่มต้นค่าเริ่มต้น (T) เป็นค่าว่างสำหรับประเภทการอ้างอิงทั้งหมด
แดนค.

1
ใช่รวมไว้เพื่อความสมบูรณ์จริงๆ
annakata

13

หากต้องการขยายคำตอบข้างต้นการเพิ่มwhere T:new()ข้อ จำกัด ให้กับวิธีการทั่วไปจะต้องให้ T มีตัวสร้างสาธารณะที่ไม่มีพารามิเตอร์

หากคุณต้องการหลีกเลี่ยงสิ่งนั้น - และในบางครั้งในรูปแบบโรงงานคุณบังคับให้ผู้อื่นใช้วิธีการโรงงานของคุณและไม่ได้ผ่านตัวสร้างโดยตรงทางเลือกคือใช้การสะท้อน ( Activator.CreateInstance...) และทำให้ตัวสร้างเริ่มต้นเป็นส่วนตัว แต่สิ่งนี้มาพร้อมกับโทษประสิทธิภาพแน่นอน


ไม่ใช่ครั้งแรกที่คนโหวต "คำตอบอื่น ๆ ทั้งหมด" :)
Dan C.

ฉันจะยอมรับว่าบางครั้งไม่ได้โหวตคำตอบที่ 'แข่งขันกัน' อย่างดุเดือดจนกว่าผู้ตัดสินจะตัดสินคำถาม: DI เดาว่ากรรม (ที่ไม่ใช่คะแนน) จะแยกพวกเขาออก
Ruben Bartelink

8

คุณต้องการใหม่ T () แต่คุณยังจะต้องเพิ่ม, new()ไปยังwhereข้อมูลจำเพาะสำหรับวิธีการโรงงาน


ฉันชนมันสำรองฉันเข้าใจมันช่วยได้ดูเหมือนว่าคนทั่วไปชอบโพสต์โค้ดดีกว่าคำอธิบายที่นี่
Edward Tanguay

ขอบคุณโลกนี้มีเหตุผลอีกครั้ง!
Ruben Bartelink

ถูกต้อง แต่คำตอบของคุณเป็นที่ยอมรับในด้านสั้น ๆ ;)
Lorenz Lo Sauer

4

เก่าไปหน่อย แต่สำหรับคนอื่นที่กำลังมองหาวิธีแก้ปัญหานี้อาจเป็นที่สนใจ: http://daniel.wertheim.se/2011/12/29/c-generic-factory-with-support-for-private-constructors/

สองวิธีแก้ปัญหา หนึ่งใช้ Activator และอีกอันใช้คอมไพล์แลมบ์ดาส

//Person has private ctor
var person = Factory<Person>.Create(p => p.Name = "Daniel");

public static class Factory<T> where T : class 
{
    private static readonly Func<T> FactoryFn;

    static Factory()
    {
        //FactoryFn = CreateUsingActivator();

        FactoryFn = CreateUsingLambdas();
    }

    private static Func<T> CreateUsingActivator()
    {
        var type = typeof(T);

        Func<T> f = () => Activator.CreateInstance(type, true) as T;

        return f;
    }

    private static Func<T> CreateUsingLambdas()
    {
        var type = typeof(T);

        var ctor = type.GetConstructor(
            BindingFlags.Instance | BindingFlags.CreateInstance |
            BindingFlags.NonPublic,
            null, new Type[] { }, null);

        var ctorExpression = Expression.New(ctor);
        return Expression.Lambda<Func<T>>(ctorExpression).Compile();
    }

    public static T Create(Action<T> init)
    {
        var instance = FactoryFn();

        init(instance);

        return instance;
    }
}

2

คุณยังสามารถใช้การสะท้อนเพื่อดึงตัวสร้างของวัตถุและสร้างอินสแตนซ์ในลักษณะนั้น:

var c = typeof(T).GetConstructor();
T t = (T)c.Invoke();

1

การใช้คลาสโรงงานเพื่อสร้างวัตถุของคุณด้วยนิพจน์ lamba ที่คอมไพล์แล้ว: วิธีที่เร็วที่สุดที่ฉันพบในการสร้างอินสแตนซ์ประเภททั่วไป

public static class FactoryContructor<T>
{
    private static readonly Func<T> New =
        Expression.Lambda<Func<T>>(Expression.New(typeof (T))).Compile();

    public static T Create()
    {
        return New();
    }
}

นี่คือขั้นตอนที่ฉันทำตามเพื่อตั้งค่าเกณฑ์มาตรฐาน

สร้างวิธีการทดสอบเกณฑ์มาตรฐานของฉัน:

static void Benchmark(Action action, int iterationCount, string text)
{
    GC.Collect();
    var sw = new Stopwatch();
    action(); // Execute once before

    sw.Start();
    for (var i = 0; i <= iterationCount; i++)
    {
        action();
    }

    sw.Stop();
    System.Console.WriteLine(text + ", Elapsed: {0}ms", sw.ElapsedMilliseconds);
}

ฉันได้ลองใช้วิธีการจากโรงงานด้วย:

public static T FactoryMethod<T>() where T : new()
{
    return new T();
}

สำหรับการทดสอบฉันได้สร้างคลาสที่ง่ายที่สุด:

public class A { }

สคริปต์ที่จะทดสอบ:

const int iterations = 1000000;
Benchmark(() => new A(), iterations, "new A()");
Benchmark(() => FactoryMethod<A>(), iterations, "FactoryMethod<A>()");
Benchmark(() => FactoryClass<A>.Create(), iterations, "FactoryClass<A>.Create()");
Benchmark(() => Activator.CreateInstance<A>(), iterations, "Activator.CreateInstance<A>()");
Benchmark(() => Activator.CreateInstance(typeof (A)), iterations, "Activator.CreateInstance(typeof (A))");

ผลลัพธ์มากกว่า 1,000,000 การทำซ้ำ:

ใหม่ A (): 11ms

วิธีการโรงงาน A (): 275ms

FactoryClass A .Create (): 56ms

Activator.CreateInstance A (): 235ms

Activator.CreateInstance (typeof (A)): 157ms

หมายเหตุ : ฉันได้ทดสอบโดยใช้ทั้ง. NET Framework 4.5 และ 4.6 (ผลลัพธ์เทียบเท่า)


0

แทนที่จะสร้างฟังก์ชันเพื่อสร้างอินสแตนซ์ประเภท

public T InstantiateType<T>(string firstName, string lastName) where T : IPerson, new()
    {
        T obj = new T();
        obj.FirstName = firstName;
        obj.LastName = lastName;
        return obj;
    }

คุณทำได้แบบนี้

T obj = new T { FirstName = firstName, LastName = lastname };

1
สิ่งนี้ไม่ตอบคำถามที่ถูกถาม ปัญหาที่แท้จริงคือเขาต้องสร้างอินสแตนซ์ใหม่ของคลาสทั่วไป บางทีอาจไม่ได้ตั้งใจ แต่ดูเหมือนว่าคุณกำลังบอกว่าการใช้ initializer จะช่วยแก้ปัญหาเดิมได้ แต่ก็ไม่ได้ new()จำกัด เป็นสิ่งจำเป็นที่ยังคงอยู่ในประเภททั่วไปสำหรับคำตอบของคุณในการทำงาน
ผู้ใช้

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