Tạo thể hiện của kiểu chung mà hàm tạo của nó yêu cầu một tham số?


230

Nếu BaseFruitcó một hàm tạo chấp nhận một int weight, tôi có thể khởi tạo một miếng trái cây theo phương pháp chung như thế này không?

public void AddFruit<T>()where T: BaseFruit{
    BaseFruit fruit = new T(weight); /*new Apple(150);*/
    fruit.Enlist(fruitManager);
}

Một ví dụ được thêm vào đằng sau ý kiến. Có vẻ như tôi chỉ có thể làm điều này nếu tôi đưa ra BaseFruitmột hàm tạo không tham số và sau đó điền vào mọi thứ thông qua các biến thành viên. Trong mã thực sự của tôi (không phải về trái cây), điều này là không thực tế.

-Cập nhật-
Vì vậy, có vẻ như nó không thể được giải quyết bằng các ràng buộc theo bất kỳ cách nào sau đó. Từ các câu trả lời, có ba giải pháp ứng cử viên:

  • Mô hình nhà máy
  • Suy tư
  • Kích hoạt

Tôi có xu hướng nghĩ rằng sự phản chiếu là ít sạch nhất, nhưng tôi không thể quyết định giữa hai cái kia.


1
BTW: hôm nay tôi có thể sẽ giải quyết vấn đề này với thư viện IoC được lựa chọn.
Boris Callens

Reflection và Activator thực sự có liên quan chặt chẽ.
Rob Vermeulen

Câu trả lời:


335

Ngoài ra một ví dụ đơn giản hơn:

return (T)Activator.CreateInstance(typeof(T), new object[] { weight });

Lưu ý rằng việc sử dụng ràng buộc () mới trên T chỉ để làm cho trình biên dịch kiểm tra một hàm tạo không tham số công khai tại thời điểm biên dịch, mã thực tế được sử dụng để tạo kiểu là lớp Activator.

Bạn sẽ cần đảm bảo bản thân về hàm tạo cụ thể hiện có và loại yêu cầu này có thể là mùi mã (hoặc đúng hơn là thứ bạn chỉ nên cố gắng tránh trong phiên bản hiện tại trên c #).


Vì hàm tạo này nằm trên lớp cơ sở (BaseFbean) nên tôi biết nó sẽ có một hàm tạo. Nhưng thực sự, nếu một ngày tôi quyết định bưởi cần nhiều thông số hơn, tôi có thể bị lừa. Sẽ nhìn vào lớp AC activator mặc dù. Không nghe thấy trước đây.
Boris Callens

3
Điều này làm việc tốt. Ngoài ra còn có một quy trình <T> () của CreatInstance, nhưng điều đó không gây quá tải cho các tham số cho một số rason ..
Boris Callens

20
Không có nhu cầu sử dụng new object[] { weight }. CreateInstanceđược khai báo với params public static object CreateInstance(Type type, params object[] args), vì vậy bạn chỉ có thể làm return (T) Activator.CreateInstance(typeof(T), weight);. Nếu có nhiều tham số, chuyển chúng thành các đối số riêng biệt. Chỉ khi bạn đã có sẵn vô số các tham số được xây dựng thì bạn mới nên chuyển đổi nó sang object[]và chuyển nó sang CreateInstance.
ErikE

2
Điều này sẽ có vấn đề hiệu suất tôi đã đọc. Sử dụng một lambda biên dịch thay thế. vagifabilov.wordpress.com/2010/04/02/ Khăn
David

1
@RobVermeulen - Tôi nghĩ một cái gì đó giống như một thuộc tính tĩnh trên mỗi lớp Fruit, có chứa một Funccái tạo ra thể hiện mới. Giả sử sử Appledụng constructor là new Apple(wgt). Sau đó thêm vào Applelớp định nghĩa này: static Func<float, Fruit> CreateOne { get; } = (wgt) => new Apple(wgt);Trong Factory xác định public static Fruit CreateFruitGiven(float weight, Func<float, Fruit> createOne) { return createOne(weight); } Cách sử dụng: Factory.CreateFruit(57.3f, Apple.CreateOne);- tạo và trả về một Apple, với weight=57.3f.
ToolmakerSteve

92

Bạn không thể sử dụng bất kỳ hàm tạo tham số nào. Bạn có thể sử dụng hàm tạo không tham số nếu bạn có where T : new()ràng buộc "".

Đó là một nỗi đau, nhưng đó là cuộc sống :(

Đây là một trong những điều tôi muốn giải quyết với "giao diện tĩnh" . Sau đó, bạn có thể giới hạn T để bao gồm các phương thức tĩnh, toán tử và hàm tạo, sau đó gọi chúng.


2
Ít nhất bạn CÓ THỂ làm những ràng buộc như vậy - Java luôn làm tôi thất vọng.
Marcel Jackwerth

@JonSkeet: Nếu tôi tiếp xúc API với .NET generic sẽ được gọi trong VB6.0..Có còn khả thi không?
Roy Lee

@Roylee: Tôi không có ý kiến ​​gì, nhưng tôi nghi ngờ là không.
Jon Skeet

Tôi nghĩ rằng các giao diện tĩnh có thể được thêm vào bởi một trình biên dịch ngôn ngữ mà không thay đổi thời gian chạy, mặc dù sẽ rất tốt nếu có các nhóm ngôn ngữ phối hợp trên các chi tiết. Chỉ định rằng mọi lớp yêu cầu thực hiện giao diện tĩnh phải chứa một lớp lồng với một tên liên quan đến giao diện cụ thể, định nghĩa một thể hiện đơn lẻ tĩnh của kiểu riêng. Liên kết với giao diện sẽ là một loại chung tĩnh với trường đối tượng cần được tải với singleton một lần qua Reflection, nhưng có thể được sử dụng trực tiếp sau đó.
supercat

Một ràng buộc của hàm tạo được tham số hóa có thể được xử lý theo cùng một cách (sử dụng phương thức nhà máy và tham số chung cho kiểu trả về của nó); trong mọi trường hợp, mọi thứ đều không thể ngăn mã được viết bằng ngôn ngữ không hỗ trợ tính năng đó yêu cầu thực hiện giao diện mà không xác định loại tĩnh thích hợp, do đó mã được viết bằng các ngôn ngữ đó có thể bị lỗi khi chạy, nhưng người dùng có thể tránh được Reflection mã.
supercat

61

Đúng; thay đổi vị trí của bạn:

where T:BaseFruit, new()

Tuy nhiên, điều này chỉ hoạt động với các nhà xây dựng không tham số . Bạn sẽ phải có một số phương tiện khác để thiết lập tài sản của bạn (tự đặt tài sản hoặc một cái gì đó tương tự).


Nếu nhà xây dựng không có tham số thì điều này có vẻ an toàn với tôi.
PerpetualStudent

Bạn đã cứu mạng tôi. Tôi không thể quản lý để hạn chế T đối với lớp và từ khóa mới ().
Kiểu gen

28

Giải pháp đơn giản nhất Activator.CreateInstance<T>()


1
Cảm ơn về lời đề nghị, nó đã đưa tôi đến nơi tôi cần. Mặc dù điều này không cho phép bạn sử dụng một hàm tạo tham số. Nhưng bạn có thể sử dụng biến thể không chung chung: Activator.CreateInstance (typeof (T), đối tượng mới [] {...}) trong đó mảng đối tượng chứa các đối số cho hàm tạo.
Rob Vermeulen

19

Như Jon chỉ ra đây là cuộc sống để ràng buộc một nhà xây dựng không tham số. Tuy nhiên, một giải pháp khác là sử dụng mô hình nhà máy. Điều này dễ bị hạn chế

interface IFruitFactory<T> where T : BaseFruit {
  T Create(int weight);
}

public void AddFruit<T>( IFruitFactory<T> factory ) where T: BaseFruit {    
  BaseFruit fruit = factory.Create(weight); /*new Apple(150);*/    
  fruit.Enlist(fruitManager);
}

Tuy nhiên, một lựa chọn khác là sử dụng một phương pháp chức năng. Đạt trong một phương pháp nhà máy.

public void AddFruit<T>(Func<int,T> factoryDel) where T : BaseFruit { 
  BaseFruit fruit = factoryDel(weight); /* new Apple(150); */
  fruit.Enlist(fruitManager);
}

2
Gợi ý hay - mặc dù nếu bạn không cẩn thận, bạn có thể rơi vào địa ngục của API Java DOM, với các nhà máy galore :(
Jon Skeet

Vâng, đây là một giải pháp tôi đang che giấu bản thân mình. Nhưng tôi đã hy vọng cho một cái gì đó trong dòng hạn chế. Đoán không rồi ..
Boris Callens

@boris, thật không may, ngôn ngữ ràng buộc mà bạn đang tìm kiếm không tồn tại vào thời điểm này
JaredPar

11

Bạn có thể làm bằng cách sử dụng sự phản chiếu:

public void AddFruit<T>()where T: BaseFruit
{
  ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { typeof(int) });
  if (constructor == null)
  {
    throw new InvalidOperationException("Type " + typeof(T).Name + " does not contain an appropriate constructor");
  }
  BaseFruit fruit = constructor.Invoke(new object[] { (int)150 }) as BaseFruit;
  fruit.Enlist(fruitManager);
}

EDIT: Đã thêm constructor == null kiểm tra.

EDIT: Một biến thể nhanh hơn bằng cách sử dụng bộ đệm:

public void AddFruit<T>()where T: BaseFruit
{
  var constructor = FruitCompany<T>.constructor;
  if (constructor == null)
  {
    throw new InvalidOperationException("Type " + typeof(T).Name + " does not contain an appropriate constructor");
  }
  var fruit = constructor.Invoke(new object[] { (int)150 }) as BaseFruit;
  fruit.Enlist(fruitManager);
}
private static class FruitCompany<T>
{
  public static readonly ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { typeof(int) });
}

Mặc dù tôi không thích phần trên của sự phản chiếu, như những người khác đã giải thích, đây chỉ là cách nó hiện tại. Xem làm thế nào nhà xây dựng này sẽ không được gọi quá nhiều, tôi có thể đi với điều này. Hoặc nhà máy. Chưa biết.
Boris Callens

Đây hiện là cách tiếp cận ưa thích của tôi vì nó không làm tăng thêm sự phức tạp ở phía gọi.
Rob Vermeulen

Nhưng bây giờ tôi đã đọc về đề xuất Activator, có độ khó tương tự như giải pháp phản chiếu ở trên, nhưng với ít dòng mã hơn :) Tôi sẽ sử dụng tùy chọn Activator.
Rob Vermeulen

1

Là một bổ sung cho đề xuất của user1471935:

Để khởi tạo một lớp chung bằng cách sử dụng một hàm tạo với một hoặc nhiều tham số, bây giờ bạn có thể sử dụng lớp Activator.

T instance = Activator.CreateInstance(typeof(T), new object[] {...}) 

Danh sách các đối tượng là các tham số bạn muốn cung cấp. Theo Microsoft :

CreateInstance [...] tạo một thể hiện của loại được chỉ định bằng cách sử dụng hàm tạo phù hợp nhất với các tham số đã chỉ định.

Ngoài ra còn có một phiên bản chung của CreatInstance ( CreateInstance<T>()) nhưng phiên bản đó cũng không cho phép bạn cung cấp các tham số của hàm tạo.


1

Tôi đã tạo phương pháp này:

public static V ConvertParentObjToChildObj<T,V> (T obj) where V : new()
{
    Type typeT = typeof(T);
    PropertyInfo[] propertiesT = typeT.GetProperties();
    V newV = new V();
    foreach (var propT in propertiesT)
    {
        var nomePropT = propT.Name;
        var valuePropT = propT.GetValue(obj, null);

        Type typeV = typeof(V);
        PropertyInfo[] propertiesV = typeV.GetProperties();
        foreach (var propV in propertiesV)
        {
            var nomePropV = propV.Name;
            if(nomePropT == nomePropV)
            {
                propV.SetValue(newV, valuePropT);
                break;
            }
        }
    }
    return newV;
}

Tôi sử dụng nó theo cách này:

public class A 
{
    public int PROP1 {get; set;}
}

public class B : A
{
    public int PROP2 {get; set;}
}

Mã số:

A instanceA = new A();
instanceA.PROP1 = 1;

B instanceB = new B();
instanceB = ConvertParentObjToChildObj<A,B>(instanceA);

0

Gần đây tôi đã gặp một vấn đề rất giống nhau. Chỉ muốn chia sẻ giải pháp của chúng tôi với tất cả các bạn. Tôi muốn tôi tạo một thể hiện của một Car<CarA>đối tượng json bằng cách sử dụng enum:

Dictionary<MyEnum, Type> mapper = new Dictionary<MyEnum, Type>();

mapper.Add(1, typeof(CarA));
mapper.Add(2, typeof(BarB)); 

public class Car<T> where T : class
{       
    public T Detail { get; set; }
    public Car(T data)
    {
       Detail = data;
    }
}
public class CarA
{  
    public int PropA { get; set; }
    public CarA(){}
}
public class CarB
{
    public int PropB { get; set; }
    public CarB(){}
}

var jsonObj = {"Type":"1","PropA":"10"}
MyEnum t = GetTypeOfCar(jsonObj);
Type objectT = mapper[t]
Type genericType = typeof(Car<>);
Type carTypeWithGenerics = genericType.MakeGenericType(objectT);
Activator.CreateInstance(carTypeWithGenerics , new Object[] { JsonConvert.DeserializeObject(jsonObj, objectT) });

-2

Vẫn có thể, với hiệu suất cao, bằng cách thực hiện như sau:

    //
    public List<R> GetAllItems<R>() where R : IBaseRO, new() {
        var list = new List<R>();
        using ( var wl = new ReaderLock<T>( this ) ) {
            foreach ( var bo in this.items ) {
                T t = bo.Value.Data as T;
                R r = new R();
                r.Initialize( t );
                list.Add( r );
            }
        }
        return list;
    }

    //
///<summary>Base class for read-only objects</summary>
public partial interface IBaseRO  {
    void Initialize( IDTO dto );
    void Initialize( object value );
}

Các lớp có liên quan sau đó phải xuất phát từ giao diện này và khởi tạo tương ứng. Xin lưu ý rằng trong trường hợp của tôi, mã này là một phần của lớp xung quanh, đã có <T> làm tham số chung. R, trong trường hợp của tôi, cũng là một lớp chỉ đọc. IMO, tính khả dụng công khai của các hàm Khởi tạo () không có tác động tiêu cực đến tính bất biến. Người dùng của lớp này có thể đặt một đối tượng khác vào, nhưng điều này sẽ không sửa đổi bộ sưu tập cơ bản.

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.