Làm thế nào để vượt qua các loại ẩn danh như tham số?


143

Làm thế nào tôi có thể chuyển các loại ẩn danh làm tham số cho các chức năng khác? Xem xét ví dụ này:

var query = from employee in employees select new { Name = employee.Name, Id = employee.Id };
LogEmployees(query);

Biến queryở đây không có loại mạnh. Làm thế nào tôi nên xác định LogEmployeeschức năng của mình để chấp nhận nó?

public void LogEmployees (? list)
{
    foreach (? item in list)
    {

    }
}

Nói cách khác, tôi nên sử dụng cái gì thay vì ?nhãn hiệu.


1
Câu hỏi trùng lặp khác nhau tốt hơn liên quan đến việc truyền tham số thay vì trả về dữ liệu: stackoverflow.com/questions/16823658/ mẹo
Rob Church

Câu trả lời:


183

Tôi nghĩ bạn nên tạo một lớp cho loại ẩn danh này. Đó là điều hợp lý nhất để làm theo ý kiến ​​của tôi. Nhưng nếu bạn thực sự không muốn, bạn có thể sử dụng động lực học:

public void LogEmployees (IEnumerable<dynamic> list)
{
    foreach (dynamic item in list)
    {
        string name = item.Name;
        int id = item.Id;
    }
}

Lưu ý rằng điều này không được gõ mạnh, vì vậy, ví dụ, nếu Tên thay đổi thành EmployeeName, bạn sẽ không biết có vấn đề gì cho đến khi chạy.


Tôi đã kiểm tra đây là câu trả lời đúng, vì dynamiccách sử dụng. Tôi thực sự đã có ích cho tôi. Cảm ơn :)
Saeed Neamati

1
Tôi đồng ý rằng một khi dữ liệu bắt đầu được truyền xung quanh, một cách có cấu trúc chặt chẽ hơn có thể / thường được ưu tiên hơn để không giới thiệu các lỗi khó tìm (bạn đang bỏ qua hệ thống loại). Tuy nhiên, nếu bạn muốn tìm một sự thỏa hiệp, một cách khác là chỉ cần vượt qua một Từ điển chung chung. Khởi tạo từ điển C # khá thuận tiện để sử dụng những ngày này.
Jonas

Có một số trường hợp bạn muốn triển khai chung và chuyển các loại cứng có nghĩa là có thể chuyển đổi hoặc thực hiện tại nhà máy bắt đầu làm mờ mã. Nếu bạn có một tình huống thực sự năng động và không ngại một chút suy nghĩ để xử lý dữ liệu bạn nhận được, thì điều này là hoàn hảo. Cảm ơn câu trả lời @Tim S.
Larry Smith

42

Bạn có thể làm như thế này:

public void LogEmployees<T>(List<T> list) // Or IEnumerable<T> list
{
    foreach (T item in list)
    {

    }
}

... nhưng bạn sẽ không phải làm gì nhiều với mỗi mục. Bạn có thể gọi ToString, nhưng bạn sẽ không thể sử dụng (nói) NameIdtrực tiếp.


2
Ngoại trừ bạn có thể sử dụng where T : some typeở cuối dòng đầu tiên để thu hẹp loại. Tuy nhiên, tại thời điểm đó, mong đợi một loại giao diện chung nhất định sẽ có ý nghĩa hơn để mong đợi một giao diện. :)
CassOnMars

9
@d_r_w: where T : some typeMặc dù vậy, bạn không thể sử dụng với các loại ẩn danh vì chúng không triển khai bất kỳ loại giao diện nào ...
Jon Skeet

@dlev: Bạn không thể làm điều đó, foreach yêu cầu biến được lặp khi triển khai GetEnumerator và các loại ẩn danh không đảm bảo điều đó.
CassOnMars

1
@Jon Skeet: điểm tốt, bộ não của tôi không đủ sức sáng nay.
CassOnMars

1
@JonSkeet. Tôi cho rằng bạn có thể sử dụng sự phản chiếu để vẫn truy cập / thiết lập các thuộc tính nếu T là một loại ẩn danh phải không? Tôi đang nghĩ về trường hợp ai đó viết câu lệnh "Chọn * từ" và sử dụng lớp ẩn danh (hoặc được xác định) để xác định cột nào từ bản đồ kết quả truy vấn đến cùng thuộc tính có tên trên đối tượng ẩn danh của bạn.
C. Tewalt

19

Thật không may, những gì bạn đang cố gắng làm là không thể. Dưới mui xe, biến truy vấn được nhập là một IEnumerableloại ẩn danh. Tên loại ẩn danh không thể được biểu diễn trong mã người dùng do đó không có cách nào biến chúng thành tham số đầu vào cho hàm.

Đặt cược tốt nhất của bạn là tạo một loại và sử dụng nó như là sự trở lại từ truy vấn và sau đó chuyển nó vào hàm. Ví dụ,

struct Data {
  public string ColumnName; 
}

var query = (from name in some.Table
            select new Data { ColumnName = name });
MethodOp(query);
...
MethodOp(IEnumerable<Data> enumerable);

Tuy nhiên, trong trường hợp này, bạn chỉ chọn một trường duy nhất, vì vậy có thể dễ dàng hơn khi chỉ chọn trường trực tiếp. Điều này sẽ khiến truy vấn được nhập dưới dạng một IEnumerablekiểu trường. Trong trường hợp này, tên cột.

var query = (from name in some.Table select name);  // IEnumerable<string>

Ví dụ của tôi là một, nhưng hầu hết thời gian là nhiều hơn. Câu trả lời của bạn thông qua các tác phẩm (và khá rõ ràng bây giờ). Tôi chỉ cần nghỉ trưa để suy nghĩ thôi ;-)
Tony Trembath-Drake

FYI: được hợp nhất từ stackoverflow.com/questions/775387/
Shog9

Một cảnh báo là khi bạn tạo một lớp thích hợp Equalssẽ thay đổi hành vi. Tức là bạn phải thực hiện nó. (Tôi biết về sự khác biệt này nhưng vẫn cố quên nó trong quá trình tái cấu trúc.)
LosManos

11

Bạn không thể chuyển một loại ẩn danh cho một hàm không chung chung, trừ khi loại tham số là object.

public void LogEmployees (object obj)
{
    var list = obj as IEnumerable(); 
    if (list == null)
       return;

    foreach (var item in list)
    {

    }
}

Các loại ẩn danh được dự định để sử dụng ngắn hạn trong một phương thức.

Từ MSDN - Các loại ẩn danh :

Bạn không thể khai báo một trường, một thuộc tính, một sự kiện hoặc kiểu trả về của một phương thức là có một kiểu ẩn danh. Tương tự, bạn không thể khai báo một tham số chính thức của một phương thức, thuộc tính, hàm tạo hoặc bộ chỉ mục là có một kiểu ẩn danh. Để truyền một loại ẩn danh hoặc một bộ sưu tập có chứa các loại ẩn danh, làm đối số cho một phương thức, bạn có thể khai báo tham số là đối tượng loại . Tuy nhiên, làm điều này đánh bại mục đích đánh máy mạnh mẽ.

(nhấn mạnh của tôi)


Cập nhật

Bạn có thể sử dụng thuốc generic để đạt được những gì bạn muốn:

public void LogEmployees<T>(IEnumerable<T> list)
{
    foreach (T item in list)
    {

    }
}

4
Nếu bạn không thể chuyển các loại ẩn danh (hoặc các bộ sưu tập của một loại ẩn danh) cho các phương thức, toàn bộ LINQ sẽ thất bại. Bạn có thể, chỉ là phương thức phải hoàn toàn chung chung, không sử dụng các thuộc tính của loại ẩn danh.
Jon Skeet

2
lại object- hoặc dynamic; p
Marc Gravell

Nếu truyền với "là", bạn nên kiểm tra xem danh sách có rỗng không
Alex

"có thể"! = "phải". Sử dụng objectkhông giống như tạo một phương thức chung trong loại ẩn danh, theo câu trả lời của tôi.
Jon Skeet

8

Thông thường, bạn làm điều này với thuốc generic, ví dụ:

MapEntToObj<T>(IQueryable<T> query) {...}

Trình biên dịch sẽ suy ra Tkhi bạn gọi MapEntToObj(query). Không hoàn toàn chắc chắn những gì bạn muốn làm bên trong phương thức, vì vậy tôi không thể biết liệu điều này có hữu ích không ... vấn đề là bên trong MapEntToObjbạn vẫn không thể đặt tên T- bạn có thể:

  • gọi các phương thức chung khác với T
  • sử dụng sự phản ánh Tđể làm việc

nhưng khác với điều đó, khá khó để thao túng các loại ẩn danh - không chỉ bởi vì chúng là bất biến ;-p

Một mẹo khác (khi trích xuất dữ liệu) là cũng vượt qua bộ chọn - tức là đại loại như:

Foo<TSource, TValue>(IEnumerable<TSource> source,
        Func<TSource,string> name) {
    foreach(TSource item in source) Console.WriteLine(name(item));
}
...
Foo(query, x=>x.Title);

1
Đã học được điều gì đó mới, không biết rằng các loại ẩn danh là bất biến! ;)
Annie Lagang

1
@AnneLagang thực sự tùy thuộc vào trình biên dịch, vì nó tạo ra chúng. Trong VB.NET, các loại anon có thể thay đổi.
Marc Gravell

1
FYI: được hợp nhất từ stackoverflow.com/questions/775387/
Shog9

7

Bạn có thể sử dụng thuốc generic với thủ thuật sau (chuyển sang loại ẩn danh):

public void LogEmployees<T>(IEnumerable<T> list)
{
    foreach (T item in list)
    {
        var typedItem = Cast(item, new { Name = "", Id = 0 });
        // now you can use typedItem.Name, etc.
    }
}

static T Cast<T>(object obj, T type)
{
    return (T)obj;
}

6

"Động" cũng có thể được sử dụng cho mục đích này.

var anonymousType = new { Id = 1, Name = "A" };

var anonymousTypes = new[] { new { Id = 1, Name = "A" }, new { Id = 2, Name = "B" };

private void DisplayAnonymousType(dynamic anonymousType)
{
}

private void DisplayAnonymousTypes(IEnumerable<dynamic> anonymousTypes)
{
   foreach (var info in anonymousTypes)
   {

   }
}

1
Đây là câu trả lời đúng! Nó chỉ cần thêm tình yêu :)
Korayem

2

Thay vì chuyển một loại ẩn danh, hãy chuyển Danh sách loại động:

  1. var dynamicResult = anonymousQueryResult.ToList<dynamic>();
  2. Chữ ký phương thức: DoSomething(List<dynamic> _dynamicResult)
  3. Phương thức gọi: DoSomething(dynamicResult);
  4. làm xong.

Cảm ơn Petar Ivanov !


0

Nếu bạn biết, kết quả của bạn thực hiện một giao diện nhất định, bạn có thể sử dụng giao diện đó dưới dạng kiểu dữ liệu:

public void LogEmployees<T>(IEnumerable<T> list)
{
    foreach (T item in list)
    {

    }
}

0

Tôi sẽ sử dụng IEnumerable<object>như là loại cho các đối số. Tuy nhiên không phải là một lợi ích lớn cho các diễn viên rõ ràng không thể tránh khỏi. Chúc mừ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.