Di C #, bagaimana cara membuat instance tipe generik yang diteruskan di dalam metode?

98

Bagaimana saya bisa memberi contoh tipe T di dalam InstantiateType<T>metode saya di bawah ini?

Saya mendapatkan kesalahan: 'T' adalah 'parameter tipe' tetapi digunakan seperti 'variabel'. :

(GULIR KE BAWAH UNTUK JAWABAN DEFAKTORKAN)

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

JAWABAN YANG DITERBITKAN:

Terima kasih atas semua komentarnya, mereka membuat saya di jalur yang benar, inilah yang ingin saya lakukan:

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; }
    }
}
Edward Tanguay
sumber
1 untuk beralih ke pola desain yang lebih baik.
Joel Coehoorn
1 untuk kode yang diketik dengan sangat rapi, jarang.
nawfal

Jawaban:

131

Nyatakan metode Anda seperti ini:

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

Perhatikan kendala tambahan di bagian akhir. Kemudian buat sebuah newinstance di dalam metode body:

T obj = new T();    
Joel Coehoorn
sumber
4
Saya telah menulis C # selama bertahun-tahun dengan beberapa penyalahgunaan pengetikan umum yang berat di hari-hari saya, dan saya TIDAK PERNAH tahu Anda dapat mendefinisikan batasan seperti ini untuk membuat contoh tipe generik. Terimakasih banyak!
Nicolas Martel
sangat sangat bagus!!
Sotiris Zegiannis
bagaimana jika TIDAK ada tipe yang ditentukan, Apakah itu mungkin?
jj
31

Beberapa cara.

Tanpa menentukan tipe harus memiliki konstruktor:

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

Dengan konstruktor:

T obj = new T();

Tapi ini membutuhkan klausul:

where T : new()
annakata
sumber
1
Yang pertama akan menetapkan null daripada membuat instance untuk tipe referensi.
Joel Coehoorn
1
Ya. Anda perlu menggunakan refleksi untuk membuat tipe tanpa konstruktor default, default (T) adalah null untuk semua tipe referensi.
Dan C.
1
Ya tentu saja termasuk untuk kelengkapan banget.
annakata
13

Untuk memperluas jawaban di atas, menambahkan where T:new() batasan ke metode umum akan membutuhkan T untuk memiliki konstruktor publik tanpa parameter.

Jika Anda ingin menghindarinya - dan dalam pola pabrik terkadang Anda memaksa yang lain untuk menggunakan metode pabrik Anda dan tidak secara langsung melalui konstruktor - maka alternatifnya adalah menggunakan refleksi ( Activator.CreateInstance...) dan menjaga agar konstruktor default tetap pribadi. Tapi ini datang dengan penalti performa, tentu saja.

Dan C.
sumber
Ini bukan pertama kalinya orang tidak menyukai "semua jawaban lain" :)
Dan C.
Saya akan mengakui kadang-kadang dengan kejam tidak menaikkan jawaban 'bersaing' sampai dusgt menyelesaikan sebuah pertanyaan: Saya menebak (non-poin) karma akan menyelesaikannya!
Ruben Bartelink
8

Anda ingin baru T (), tetapi Anda juga harus menambahkan , new()ke wherespec untuk metode pabrik

Ruben Bartelink
sumber
saya menemukannya kembali, saya memahaminya, membantu, tampaknya secara umum orang lebih menyukai kode yang diposting lebih baik daripada deskripsi di sini
Edward Tanguay
Terima kasih, dunia menjadi masuk akal lagi!
Ruben Bartelink
benar tetapi jawaban Anda memang agak pendek;)
Lorenz Lo Sauer
4

Agak tua tetapi untuk orang lain yang mencari solusi, mungkin ini bisa menarik: http://daniel.wertheim.se/2011/12/29/c-generic-factory-with-support-for-private-constructors/

Dua solusi. Satu menggunakan Activator dan satu lagi menggunakan Compiled Lambdas.

//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;
    }
}
Daniel
sumber
2

Anda juga dapat menggunakan refleksi untuk mengambil konstruktor objek dan membuat instance seperti itu:

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

Menggunakan kelas pabrik untuk membangun objek Anda dengan ekspresi lamba terkompilasi: Cara tercepat yang saya temukan untuk membuat instance tipe generik.

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

Berikut adalah langkah-langkah yang saya ikuti untuk menyiapkan patokan.

Buat metode pengujian benchmark saya:

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

Saya juga mencoba menggunakan metode pabrik:

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

Untuk tes saya telah membuat kelas paling sederhana:

public class A { }

Script yang akan diuji:

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))");

Menghasilkan lebih dari 1.000.000 iterasi:

baru A (): 11ms

FactoryMethod A (): 275ms

Pabrik Kelas A. Buat (): 56ms

Aktivator.CreateInstance A (): 235ms

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

Catatan : Saya telah menguji menggunakan .NET Framework 4.5 dan 4.6 (hasil yang setara).

Thomas
sumber
0

Alih-alih membuat fungsi untuk membuat instance tipe

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

Anda bisa melakukannya seperti ini

T obj = new T { FirstName = firstName, LastName = lastname };
TMul
sumber
1
Ini tidak menjawab pertanyaan yang ditanyakan. Masalah sebenarnya di sini adalah dia perlu membuat instance baru dari kelas generik. Mungkin itu tidak disengaja, tetapi sepertinya Anda mengatakan bahwa menggunakan penginisialisasi akan menyelesaikan masalah asli, tetapi ternyata tidak. The new()kendala masih diperlukan pada jenis generik untuk jawaban Anda bekerja.
Pengguna
Jika Anda mencoba untuk membantu dan menyarankan bahwa penginisialisasi adalah alat yang berguna di sini, maka Anda harus mempostingnya sebagai komentar, bukan jawaban lain.
Pengguna