Trong C #, làm thế nào để khởi tạo một kiểu chung được truyền vào bên trong một phương thức?


98

Làm cách nào để tạo kiểu T bên trong InstantiateType<T>phương thức của tôi dưới đây?

Tôi gặp lỗi: 'T' là một 'tham số kiểu' nhưng được sử dụng giống như một 'biến'. :

(CUỘC XUỐNG ĐỂ CÓ CÂU TRẢ LỜI ĐƯỢC CHẾ TẠO)

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

CÂU TRẢ LỜI ĐƯỢC CHẾ TẠO:

Cảm ơn tất cả các nhận xét, họ đã giúp tôi đi đúng hướng, đây là điều tôi muốn làm:

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 để chuyển sang mẫu thiết kế tốt hơn.
Joel Coehoorn

+1 cho mã được gõ cực kỳ gọn gàng, một điều hiếm thấy.
nawfal

Câu trả lời:


131

Khai báo phương thức của bạn như sau:

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

Lưu ý ràng buộc bổ sung ở cuối. Sau đó, tạo một newthể hiện trong thân phương thức:

T obj = new T();    

4
Tôi đã viết C # trong nhiều năm với một số lạm dụng gõ chung chung nặng nề trong những ngày của tôi và tôi KHÔNG BAO GIỜ biết bạn có thể xác định một ràng buộc như thế này để khởi tạo một kiểu chung chung. Cảm ơn nhiều!
Nicolas Martel

rất rất tốt!!
Sotiris Zegiannis

điều gì xảy ra nếu KHÔNG loại được chỉ định, Điều đó có thể?
jj

31

Một số cách.

Không chỉ định kiểu phải có một hàm tạo:

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

Với một hàm tạo:

T obj = new T();

Nhưng điều này yêu cầu điều khoản:

where T : new()

1
Cái đầu tiên sẽ gán null thay vì tạo một thể hiện cho các kiểu tham chiếu.
Joel Coehoorn

1
Vâng. Bạn cần sử dụng phản xạ để tạo các kiểu mà không có hàm tạo mặc định, mặc định (T) là null cho tất cả các kiểu tham chiếu.
Dan C.

1
Yep hoàn toàn, bao gồm cho sự hoàn chỉnh thực sự.
annakata

13

Để mở rộng các câu trả lời ở trên, việc thêm where T:new()ràng buộc vào một phương thức chung sẽ yêu cầu T phải có một hàm tạo không tham số, công khai.

Nếu bạn muốn tránh điều đó - và trong một factory pattern, đôi khi bạn buộc những người khác phải đi qua phương thức factory của bạn chứ không phải trực tiếp thông qua hàm tạo - thì giải pháp thay thế là sử dụng phản xạ ( Activator.CreateInstance...) và giữ hàm tạo mặc định ở chế độ riêng tư. Nhưng điều này đi kèm với một hình phạt về hiệu suất, tất nhiên.


Đây không phải là lần đầu tiên mọi người phản đối "tất cả các câu trả lời khác" :)
Dan C.

Tôi thừa nhận rằng đôi khi sẽ không ủng hộ các câu trả lời 'cạnh tranh' cho đến khi người dusgt giải quyết được một câu hỏi: DI đoán (không phải điểm) nghiệp sẽ sắp xếp chúng!
Ruben Bartelink

8

bạn muốn mới T (), nhưng bạn cũng sẽ cần phải thêm , new()vào wherespec cho phương pháp nhà máy


tôi tình cờ gặp nó sao lưu, tôi hiểu nó, giúp, có vẻ như ở những người chung chung như mã đăng tốt hơn so với giới thiệu ở đây
Edward Tanguay

Cảm ơn, thế giới lại có ý nghĩa!
Ruben Bartelink

sửa nhưng câu trả lời của bạn là thừa nhận một chút ở phía bên ngắn;)
Lorenz Lo Sauer

4

Hơi cũ nhưng đối với những người khác đang tìm kiếm giải pháp, có lẽ điều này có thể quan tâm: http://daniel.wertheim.se/2011/12/29/c-generic-factory-with-support-for-private-constructors/

Hai giải pháp. Một sử dụng Activator và một sử dụng Lambdas biên dịch.

//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

Bạn cũng có thể sử dụng sự phản chiếu để tìm nạp hàm tạo của đối tượng và khởi tạo theo cách đó:

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

1

Sử dụng một lớp nhà máy để xây dựng đối tượng của bạn với biểu thức lamba đã biên dịch: Cách nhanh nhất mà tôi đã tìm thấy để khởi tạo kiểu chung.

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

Đây là các bước tôi đã làm theo để thiết lập điểm chuẩn.

Tạo phương pháp kiểm tra điểm chuẩn của tôi:

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

Tôi cũng đã thử sử dụng phương pháp factory:

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

Đối với các bài kiểm tra, tôi đã tạo lớp đơn giản nhất:

public class A { }

Tập lệnh để kiểm tra:

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

Kết quả hơn 1 000 000 lần lặp:

mới A (): 11ms

FactoryMethod A (): 275ms

FactoryClass A .Create (): 56ms

Activator.CreateInstance A (): 235ms

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

Nhận xét : Tôi đã thử nghiệm bằng cả .NET Framework 4.5 và 4.6 (kết quả tương đương).


0

Thay vì tạo một hàm để Khởi tạo kiểu

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

bạn có thể đã làm nó như thế này

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

1
Điều này không trả lời câu hỏi đang được hỏi. Vấn đề thực sự ở đây là anh ta cần tạo một phiên bản mới của lớp chung. Có lẽ đó là điều ngoài ý muốn, nhưng có vẻ như bạn đang nói rằng sử dụng trình khởi tạo sẽ giải quyết được vấn đề ban đầu, nhưng không phải vậy. Các new()hạn chế vẫn còn cần thiết vào loại chung cho câu trả lời của bạn để làm việc.
Người dùng

Nếu bạn đang cố gắng tỏ ra hữu ích và cho rằng trình khởi tạo là một công cụ hữu ích ở đây, thì bạn nên đăng nó dưới dạng nhận xét chứ không phải câu trả lời khác.
Người dùng
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.