Truyền đối số cho C # generic new () của kiểu templated


409

Tôi đang cố gắng tạo một đối tượng mới loại T thông qua hàm tạo của nó khi thêm vào danh sách.

Tôi đang gặp lỗi biên dịch: Thông báo lỗi là:

'T': không thể cung cấp đối số khi tạo phiên bản của biến

Nhưng các lớp học của tôi có một đối số constructor! Làm thế nào tôi có thể làm cho công việc này?

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
Đề xuất để có được chức năng này vào ngôn ngữ: github.com/dotnet/roslyn/issues/2206
Ian Kemp

Trong tài liệu của Microsoft, xem Lỗi Trình biên dịch CS0417 .
DavidRR

1
Đề xuất đưa chức năng này vào ngôn ngữ đã được chuyển đến: github.com/dotnet/csharplang/issues/769
giảm hoạt động

Câu trả lời:


410

Để tạo một thể hiện của một loại chung trong một hàm, bạn phải giới hạn nó với cờ "mới".

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

Tuy nhiên, điều đó sẽ chỉ hoạt động khi bạn muốn gọi hàm tạo không có tham số. Không phải trường hợp ở đây. Thay vào đó, bạn sẽ phải cung cấp một tham số khác cho phép tạo đối tượng dựa trên các tham số. Đơn giản nhất là một chức năng.

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

Sau đó bạn có thể gọi nó như vậy

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

Làm thế nào điều này sẽ làm việc khi được gọi nội bộ từ một lớp chung? Tôi đã đăng mã của tôi trong một câu trả lời dưới đây. Tôi không biết lớp cụ thể trong nội bộ, vì đây là lớp chung. Có một vòng theo cách này. Tôi không muốn sử dụng đề xuất khác về việc sử dụng cú pháp khởi tạo thuộc tính vì điều đó sẽ bỏ qua logic tôi có trong hàm tạo
ChrisCa

đã thêm mã của tôi vào một câu hỏi stackoverflow.com/questions/1682310/ khác
ChrisCa

21
Đây hiện là một trong những hạn chế khó chịu nhất của C #. Tôi muốn làm cho các lớp học của tôi không thay đổi: Chỉ có các setters riêng sẽ khiến lớp không thể ở trạng thái không hợp lệ bởi các tác dụng phụ. Tôi cũng thích sử dụng Func và lambda đó, nhưng tôi biết nó vẫn là một vấn đề trong thế giới kinh doanh vì nói chung các lập trình viên chưa biết lambdas và điều này làm cho lớp của bạn khó hiểu hơn.
Tuomas Hietanen

1
Cảm ơn. Trong trường hợp của tôi, tôi biết (các) đối số của hàm tạo khi tôi gọi phương thức, tôi chỉ cần vượt qua giới hạn của tham số Loại là nó không thể được xây dựng bằng các tham số, vì vậy tôi đã sử dụng một thunk . Thunk là một tham số tùy chọn cho phương thức và tôi chỉ sử dụng nó nếu được cung cấp: T result = thunk == null ? new T() : thunk(); Lợi ích của việc này đối với tôi là củng cố logic Ttạo ra ở một nơi thay vì đôi khi tạo Tbên trong và đôi khi bên ngoài phương thức.
Carl G

Tôi nghĩ rằng đây là một trong những nơi mà ngôn ngữ C # quyết định nói không với lập trình viên và ngừng nói đồng ý mọi lúc! Mặc dù cách tiếp cận này là một cách hơi khó để tạo đối tượng nhưng bây giờ tôi phải sử dụng nó.
AmirHossein Rezaei

331

trong .Net 3.5 và sau khi bạn có thể sử dụng lớp trình kích hoạt:

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

1
chúng ta cũng có thể sử dụng cây biểu thức để xây dựng đối tượng
Welly Tambunan

4
Luận điểm là gì? một đối tượng[]?
Rodney P. Barbati

3
Có, args là một đối tượng [] trong đó bạn chỉ định các giá trị sẽ được cung cấp cho hàm tạo của T: "đối tượng mới [] {par1, par2}"
TechNyquist


3
CẢNH BÁO: Nếu bạn có một công cụ xây dựng chuyên dụng chỉ vì mục đích Activator.CreateInstancenày, có vẻ như công cụ xây dựng của bạn hoàn toàn không được sử dụng và ai đó có thể cố gắng "dọn dẹp" và xóa nó (để gây ra lỗi thời gian chạy tại một số thời gian ngẫu nhiên trong tương lai). Bạn có thể muốn xem xét việc thêm một hàm giả trong đó bạn sử dụng hàm tạo này chỉ để bạn gặp lỗi biên dịch nếu bạn cố xóa nó.
jrh

51

Vì không ai bận tâm đăng câu trả lời 'Reflection' (mà cá nhân tôi nghĩ là câu trả lời hay nhất), nên đây:

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

Chỉnh sửa: Câu trả lời này không được chấp nhận do Activator.CreateInstance của .NET 3.5, tuy nhiên nó vẫn hữu ích trong các phiên bản .NET cũ hơn.


Sự hiểu biết của tôi là hầu hết các thành tích đạt được là trong việc có được ConstructorInfo ở vị trí đầu tiên. Đừng tin lời tôi mà không cần hồ sơ. Nếu đó là trường hợp, chỉ cần lưu trữ ConstructorInfo để sử dụng lại sau này có thể làm giảm hiệu năng của các lần lặp lại thông qua phản xạ.
Kelsie

19
Tôi nghĩ rằng việc thiếu kiểm tra thời gian biên dịch là nguyên nhân gây lo ngại nhiều hơn.
Dave Van den Eynde

1
@James Tôi đồng ý, tôi đã rất ngạc nhiên khi không xem đây là "câu trả lời". Trên thực tế, tôi đã tìm kiếm câu hỏi này với hy vọng tìm thấy một ví dụ dễ dàng (như của bạn) vì đã quá lâu kể từ khi tôi thực hiện phản xạ. Dù sao, +1 từ tôi, nhưng +1 trên câu trả lời Activator cũng vậy. Tôi đã xem xét những gì Activator đang làm, và hóa ra những gì đang làm là một số phản xạ được thiết kế rất tốt. :)
Mike

Cuộc gọi GetConstructor () rất tốn kém, vì vậy nó đáng để lưu vào bộ đệm trước vòng lặp. Bằng cách này, bằng cách chỉ gọi Invoke () bên trong vòng lặp, nó nhanh hơn rất nhiều so với việc gọi cả hai hoặc thậm chí sử dụng Activator.CreateInstance ().
Cosmin Rus

30

Đối tượng khởi tạo

Nếu hàm tạo của bạn với tham số không làm gì ngoài việc đặt thuộc tính, bạn có thể thực hiện điều này trong C # 3 hoặc tốt hơn bằng cách sử dụng trình khởi tạo đối tượng thay vì gọi hàm tạo (điều này là không thể, như đã đề cập):

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

Sử dụng điều này, bạn luôn có thể đặt bất kỳ logic xây dựng nào trong hàm tạo mặc định (trống).

Activator.CreateInstance ()

Ngoài ra, bạn có thể gọi Activator.CreateInstance () như vậy:

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

Lưu ý rằng Activator.CreateInstance có thể có một số chi phí hiệu năng mà bạn có thể muốn tránh nếu tốc độ thực thi là ưu tiên hàng đầu và một tùy chọn khác có thể duy trì cho bạn.


điều này ngăn cản việc Tbảo vệ các bất biến của nó (với điều kiện Tcó> 0 phụ thuộc hoặc các giá trị bắt buộc, giờ đây bạn có thể tạo các phiên bản Tở trạng thái không hợp lệ / không sử dụng được. Trừ khi Tmột cái gì đó đơn giản như chế độ xem DTO och, tôi sẽ nói tránh điều này.
sara

20

Câu hỏi rất cũ, nhưng câu trả lời mới ;-)

Phiên bản ExpressionTree : (Tôi nghĩ giải pháp nhanh nhất và sạch nhất)

Giống như Welly Tambunan nói, "chúng ta cũng có thể sử dụng cây biểu thức để xây dựng đối tượng"

Điều này sẽ tạo ra một 'hàm tạo' (hàm) cho loại / tham số đã cho. Nó trả về một đại biểu và chấp nhận các kiểu tham số dưới dạng một mảng các đối tượng.

Đây là:

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

Ví dụ 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;
    }
}

Sử dụng:

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

nhập mô tả hình ảnh ở đây


Một ví dụ khác: truyền các kiểu dưới dạng một mảng

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

Gỡ lỗi biểu hiện

.Lambda #Lambda1<TestExpressionConstructor.MainWindow+ConstructorDelegate>(System.Object[] $var1) {
    .New TestExpressionConstructor.MainWindow+MyClass(
        (System.Int32)$var1[0],
        (System.String)$var1[1])
}

Điều này tương đương với mã được tạo ra:

public object myConstructor(object[] var1)
{
    return new MyClass(
        (System.Int32)var1[0],
        (System.String)var1[1]);
}

Nhược điểm nhỏ

Tất cả các tham số valuetypes được đóng hộp khi chúng được truyền như một mảng đối tượng.


Kiểm tra hiệu suất đơn giản:

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

Các kết quả:

Activator: 00:00:13.8210732
Reflection: 00:00:05.2986945
Expression: 00:00:00.6681696

Sử dụng nhanh hơnExpressions +/- 8 lần so với Gọi ConstructorInfonhanh hơn +/- 20 lần so với sử dụngActivator


Bạn có hiểu biết gì về việc cần làm không nếu bạn muốn xây dựng MyClass <T> với công cụ xây dựng MyClass (dữ liệu T). Trong trường hợp này, Expression.Convert ném một ngoại lệ và nếu tôi sử dụng lớp cơ sở ràng buộc chung để chuyển đổi thành, thì Expression.New ném vì thông tin hàm tạo dành cho loại chung
Mason

@Mason (mất một lúc để trả lời ;-)) var myConstructor = CreateConstructor(typeof(MyClass<int>), typeof(int));điều này hoạt động tốt. Tôi không biết.
Jeroen van Langen

19

Điều này sẽ không làm việc trong tình huống của bạn. Bạn chỉ có thể chỉ định ràng buộc rằng nó có một hàm tạo trống:

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

Những gì bạn có thể làm là sử dụng thuộc tính tiêm bằng cách xác định giao diện này:

public interface ITakesAListItem
{
   ListItem Item { set; }
}

Sau đó, bạn có thể thay đổi phương pháp của mình thành:

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

Thay thế khác là Funcphương pháp được mô tả bởi JaredPar.


điều này sẽ bỏ qua bất kỳ logic nào trong hàm tạo có các đối số mặc dù, phải không? Tôi muốn làm một cái gì đó giống như cách tiếp cận của Jared nhưng đang gọi phương thức bên trong lớp để không biết loại cụ thể là gì ... hmmm
ChrisCa

3
Phải, cái này gọi logic của hàm tạo mặc định T (), sau đó chỉ cần đặt thuộc tính "Item". Nếu bạn đang cố gắng gọi logic của hàm tạo không mặc định, điều này sẽ không giúp ích cho bạn.
Scott Stafford

7

Bạn cần thêm nơi T: new () để cho trình biên dịch biết rằng T được đảm bảo cung cấp hàm tạo mặc định.

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

1
CẬP NHẬT: Thông báo lỗi chính xác là: 'T': không thể cung cấp đối số khi tạo phiên bản của biến
LB.

Đó là bởi vì bạn không sử dụng một hàm tạo trống, bạn đang truyền một đối số cho đối tượng đó. Không có cách nào nó có thể xử lý điều đó mà không chỉ định rằng Loại chung có tham số (đối tượng) mới.
Tối thiểu

Sau đó, bạn sẽ cần: 1. Sử dụng phản xạ 2. Truyền tham số vào phương thức khởi tạo thay vì hàm tạo, trong đó phương thức khởi tạo thuộc về giao diện mà loại của bạn thực hiện và được bao gồm trong T: ... tờ khai. Tùy chọn 1 là tác động thấp nhất cho phần còn lại của mã của bạn, nhưng tùy chọn 2 cung cấp kiểm tra thời gian biên dịch.
Richard

Đừng sử dụng sự phản chiếu! Có những cách khác như được nêu trong các câu trả lời khác giúp bạn có được hiệu quả tương tự.
Garry Shutler

@Garry - Tôi đồng ý rằng sự phản chiếu không nhất thiết là cách tiếp cận tốt nhất, nhưng nó cho phép bạn đạt được những gì cần thiết với sự thay đổi tối thiểu đối với phần còn lại của cơ sở mã. Điều đó nói rằng, tôi rất thích cách tiếp cận đại biểu nhà máy từ @JaredPar.
Richard

7

Nếu bạn chỉ đơn giản muốn khởi tạo trường thành viên hoặc thuộc tính bằng tham số hàm tạo, trong C #> = 3, bạn có thể thực hiện việc đó rất dễ dàng:

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

Đây là điều tương tự Garry Shutler đã nói, nhưng tôi muốn đặt một ghi chú quảng cáo.

Tất nhiên, bạn có thể sử dụng thủ thuật thuộc tính để làm nhiều thứ hơn là chỉ đặt giá trị trường. Một thuộc tính "set ()" có thể kích hoạt bất kỳ quá trình xử lý nào cần thiết để thiết lập các trường liên quan của nó và bất kỳ nhu cầu nào khác cho chính đối tượng, bao gồm kiểm tra xem liệu có khởi tạo đầy đủ trước khi đối tượng được sử dụng hay không, mô phỏng một sự đối lập hoàn toàn ( vâng, đó là một cách giải quyết xấu xí, nhưng nó vượt qua giới hạn mới () của M $.

Tôi không thể chắc chắn nếu đó là một lỗ có kế hoạch hoặc tác dụng phụ ngẫu nhiên, nhưng nó hoạt động.

Thật là buồn cười khi người MS thêm các tính năng mới vào ngôn ngữ và dường như không thực hiện phân tích tác dụng phụ đầy đủ. Toàn bộ điều chung chung là một bằng chứng tốt về điều này ...


1
Cả hai ràng buộc là cần thiết. InterfaceOrBaseClass làm cho trình biên dịch nhận biết trường / thuộc tính BaseMemberItem. Nếu ràng buộc "new ()" được nhận xét, nó sẽ gây ra lỗi: Lỗi 6 Không thể tạo một thể hiện của loại biến 'T' vì nó không có ràng buộc () mới
fljx

Một tình huống tôi gặp phải không giống như câu hỏi được hỏi ở đây, tuy nhiên câu trả lời này đã đưa tôi đến nơi tôi cần đến và nó dường như hoạt động rất tốt.
RubyHaus

5
Mỗi khi ai đó nhắc đến Microsoft là "M $", một phần nhỏ trong tâm hồn tôi phải chịu đựng.
Mathias Lykkegaard Lorenzen

6

Tôi thấy rằng tôi đã gặp lỗi "không thể cung cấp đối số khi tạo phiên bản của tham số loại T" vì vậy tôi cần phải làm điều này:

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

5

Nếu bạn có quyền truy cập vào lớp bạn sẽ sử dụng, bạn có thể sử dụng phương pháp này mà tôi đã sử dụng.

Tạo giao diện có trình tạo thay thế:

public interface ICreatable1Param
{
    void PopulateInstance(object Param);
}

Tạo các lớp của bạn với một trình tạo trống và thực hiện phương thức này:

public class MyClass : ICreatable1Param
{
    public MyClass() { //do something or nothing }
    public void PopulateInstance (object Param)
    {
        //populate the class here
    }
}

Bây giờ sử dụng các phương pháp chung của bạn:

public void MyMethod<T>(...) where T : ICreatable1Param, new()
{
    //do stuff
    T newT = new T();
    T.PopulateInstance(Param);
}

Nếu bạn không có quyền truy cập, hãy bọc lớp mục tiêu:

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

Đây là một loại mucky, và khi tôi nói loại mucky tôi có thể có nghĩa là nổi loạn, nhưng giả sử bạn có thể cung cấp loại tham số của bạn với một hàm tạo trống, sau đó:

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

Sẽ cho phép bạn xây dựng một đối tượng từ một loại tham số với một đối số. Trong trường hợp này, tôi giả sử hàm tạo mà tôi muốn có một đối số kiểu object. Chúng ta tạo một thể hiện giả của T bằng cách sử dụng ràng buộc cho phép hàm tạo rỗng và sau đó sử dụng sự phản chiếu để có được một trong các hàm tạo khác của nó.


0

Đôi khi tôi sử dụng một cách tiếp cận tương tự như câu trả lời bằng cách sử dụng thuộc tính, nhưng giữ cho mã sạch hơn. Thay vì có một lớp / giao diện cơ sở với một tập các thuộc tính, nó chỉ chứa một phương thức (ảo) Khởi tạo () - hoạt động như một "hàm tạo của người nghèo". Sau đó, bạn có thể để mỗi lớp xử lý việc khởi tạo riêng của nó giống như một hàm tạo, điều này cũng bổ sung một cách xử lý thuận tiện cho các chuỗi thừa kế.

Nếu thường thấy mình trong các tình huống mà tôi muốn mỗi lớp trong chuỗi khởi tạo các thuộc tính duy nhất của nó, và sau đó gọi phương thức Khởi tạo () của cha mẹ, từ đó khởi tạo các thuộc tính duy nhất của cha mẹ, v.v. Điều này đặc biệt hữu ích khi có các lớp khác nhau, nhưng có cấu trúc phân cấp tương tự, ví dụ các đối tượng kinh doanh được ánh xạ tới / từ DTO: s.

Ví dụ sử dụng từ điển chung để khởi tạo:

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

Nếu tất cả những gì bạn cần là hội tụ từ ListItem sang loại T của bạn, bạn có thể triển khai hội tụ này trong lớp T dưới dạng toán tử chuyển đổi.

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ôi tin rằng bạn phải ràng buộc T với câu lệnh where chỉ cho phép các đối tượng có hàm tạo mới.

Bây giờ nó chấp nhận mọi thứ kể cả các đối tượng không có nó.


1
Bạn có thể muốn thay đổi câu trả lời này vì câu hỏi này đã được chỉnh sửa thành câu hỏi sau khi bạn trả lời khiến câu trả lời này nằm ngoài ngữ cảnh.
đưa đón87
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.