จะส่งประเภทที่ไม่ระบุตัวตนเป็นพารามิเตอร์ได้อย่างไร


147

ฉันจะส่งประเภทที่ไม่ระบุชื่อเป็นพารามิเตอร์ไปยังฟังก์ชันอื่น ๆ ได้อย่างไร ลองพิจารณาตัวอย่างนี้:

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

ตัวแปรqueryที่นี่ไม่มีประเภทที่แข็งแกร่ง ฉันจะกำหนดLogEmployeesฟังก์ชันให้ยอมรับได้อย่างไร?

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

    }
}

กล่าวอีกนัยหนึ่งฉันควรใช้อะไรแทน?เครื่องหมาย


1
คำถามที่ซ้ำกันที่ดีกว่าที่เกี่ยวข้องกับการส่งผ่านพารามิเตอร์แทนที่จะส่งคืนข้อมูล: stackoverflow.com/questions/16823658/…
Rob Church

คำตอบ:


188

ฉันคิดว่าคุณควรสร้างคลาสสำหรับประเภทนิรนามนี้ นั่นเป็นสิ่งที่สมเหตุสมผลที่สุดที่จะทำในความคิดของฉัน แต่ถ้าคุณไม่ต้องการจริงๆคุณสามารถใช้การเปลี่ยนแปลง:

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

โปรดทราบว่าสิ่งนี้ไม่ได้พิมพ์อย่างรุนแรงดังนั้นหากชื่อเปลี่ยนเป็น EmployeeName คุณจะไม่รู้ว่ามีปัญหาจนกว่าจะรันไทม์


ฉันตรวจสอบว่านี่เป็นคำตอบที่ถูกต้องเนื่องจากdynamicการใช้งาน ฉันมีประโยชน์สำหรับฉันจริงๆ ขอบคุณ :)
Saeed Neamati

1
ฉันยอมรับว่าเมื่อข้อมูลเริ่มถูกส่งต่อไปตามปกติแล้วควรใช้วิธีที่มีโครงสร้างมากกว่านี้เพื่อที่จะไม่แนะนำจุดบกพร่องที่ยากต่อการค้นหา (คุณกำลังหลีกเลี่ยงระบบประเภท) อย่างไรก็ตามหากคุณต้องการหาทางประนีประนอมอีกวิธีหนึ่งก็คือเพียงแค่ส่งพจนานุกรมทั่วไป ตัวเริ่มต้นพจนานุกรม C # ค่อนข้างสะดวกในการใช้งานในปัจจุบัน
Jonas

มีบางกรณีที่คุณต้องการใช้งานทั่วไปและการส่งผ่านประเภทฮาร์ดอาจหมายถึงการสลับหรือการใช้งานจากโรงงานซึ่งจะทำให้โค้ดขยายตัว หากคุณมีสถานการณ์ที่ไม่หยุดนิ่งอย่างแท้จริงและไม่ต้องคำนึงถึงการไตร่ตรองเล็กน้อยเพื่อจัดการกับข้อมูลที่คุณได้รับสิ่งนี้ก็สมบูรณ์แบบ ขอบคุณสำหรับคำตอบ @Tim S.
Larry Smith

43

คุณสามารถทำได้ดังนี้:

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

    }
}

... แต่คุณจะไม่ได้ทำอะไรมากกับแต่ละรายการ คุณสามารถเรียก ToString ได้ แต่คุณจะไม่สามารถใช้ (พูด) NameและIdโดยตรงได้


2
ยกเว้นคุณสามารถใช้where T : some typeที่ท้ายบรรทัดแรกเพื่อ จำกัด ประเภทให้แคบลง ในตอนนั้นการคาดหวังว่าอินเทอร์เฟซทั่วไปบางประเภทจะเหมาะสมกว่าที่จะคาดหวังอินเทอร์เฟซ :)
CassOnMars

9
@d_r_w: คุณไม่สามารถใช้where T : some typeกับประเภทที่ไม่ระบุตัวตนได้เนื่องจากไม่ได้ใช้อินเทอร์เฟซใด ๆ ...
Jon Skeet

@dlev: คุณไม่สามารถทำได้ foreach ต้องการให้ตัวแปรวนซ้ำในการใช้ GetEnumerator และประเภทที่ไม่ระบุตัวตนไม่รับประกันสิ่งนั้น
CassOnMars

1
@ Jon Skeet: จุดดีสมองของฉันไม่ได้รับการสนับสนุนเมื่อเช้านี้
CassOnMars

1
@JonSkeet. ฉันคิดว่าคุณสามารถใช้การสะท้อนเพื่อเข้าถึง / ตั้งค่าคุณสมบัติได้ถ้า T เป็นประเภทนิรนามใช่ไหม ฉันกำลังนึกถึงกรณีที่มีคนเขียนคำสั่ง "Select * from" และใช้คลาสที่ไม่ระบุตัวตน (หรือกำหนด) เพื่อกำหนดว่าคอลัมน์ใดจากผลลัพธ์การค้นหาที่แมปกับคุณสมบัติที่มีชื่อเดียวกันบนวัตถุที่ไม่ระบุตัวตนของคุณ
C. Tewalt

19

น่าเสียดายที่สิ่งที่คุณพยายามทำนั้นเป็นไปไม่ได้ ภายใต้ฝากระโปรงตัวแปรการสืบค้นจะถูกพิมพ์ให้IEnumerableเป็นประเภทที่ไม่ระบุตัวตน ไม่สามารถแสดงชื่อชนิดที่ไม่ระบุตัวตนในรหัสผู้ใช้ได้ดังนั้นจึงไม่มีวิธีใดที่จะทำให้เป็นพารามิเตอร์อินพุตของฟังก์ชันได้

ทางออกที่ดีที่สุดของคุณคือการสร้างประเภทและใช้สิ่งนั้นเป็นผลตอบแทนจากแบบสอบถามจากนั้นจึงส่งต่อไปยังฟังก์ชัน ตัวอย่างเช่น,

struct Data {
  public string ColumnName; 
}

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

ในกรณีนี้คุณกำลังเลือกเพียงฟิลด์เดียวดังนั้นจึงอาจง่ายกว่าที่จะเลือกฟิลด์โดยตรง สิ่งนี้จะทำให้พิมพ์แบบสอบถามเป็นIEnumerableประเภทเขตข้อมูล ในกรณีนี้ชื่อคอลัมน์

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

ตัวอย่างของฉันคือหนึ่ง แต่ส่วนใหญ่แล้วจะมากกว่า คำตอบของคุณผ่านผลงาน (และค่อนข้างชัดเจนในตอนนี้) ฉันแค่ต้องการพักทานอาหารกลางวันเพื่อคิดว่า ;-)
Tony Trembath-Drake


ข้อแม้คือเมื่อคุณสร้างคลาสที่เหมาะสมEqualsจะเปลี่ยนพฤติกรรม คือคุณต้องใช้มัน (ฉันรู้ถึงความแตกต่างนี้ แต่ก็ยังลืมเรื่องนี้ได้ในระหว่างการปรับโครงสร้างใหม่)
LosManos

12

objectคุณไม่สามารถส่งผ่านชนิดระบุชื่อให้กับฟังก์ชั่นทั่วไปที่ไม่เว้นแต่ประเภทพารามิเตอร์เป็น

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

    foreach (var item in list)
    {

    }
}

ชนิดไม่ระบุชื่อมีไว้สำหรับการใช้งานระยะสั้นภายในวิธีการ

จาก MSDN - ประเภทที่ไม่ระบุตัวตน :

คุณไม่สามารถประกาศเขตข้อมูลคุณสมบัติเหตุการณ์หรือประเภทการส่งคืนของวิธีการเป็นประเภทที่ไม่ระบุตัวตน ในทำนองเดียวกันคุณไม่สามารถประกาศพารามิเตอร์ที่เป็นทางการของเมธอดคุณสมบัติตัวสร้างหรือตัวสร้างดัชนีว่ามีประเภทที่ไม่ระบุตัวตน ที่จะผ่านชนิดที่ไม่ระบุชื่อหรือคอลเลกชันที่มีประเภทที่ไม่ระบุชื่อเป็นอาร์กิวเมนต์วิธีหนึ่งที่คุณสามารถประกาศพารามิเตอร์ชนิดของวัตถุ อย่างไรก็ตามการทำเช่นนี้เอาชนะจุดประสงค์ของการพิมพ์ที่ชัดเจน

(เน้นเหมือง)


อัปเดต

คุณสามารถใช้ยาชื่อสามัญเพื่อบรรลุสิ่งที่คุณต้องการ:

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

    }
}

4
หากคุณไม่สามารถส่งต่อประเภทที่ไม่ระบุตัวตน (หรือคอลเล็กชันประเภทที่ไม่ระบุตัวตน) ไปยังเมธอด LINQ ทั้งหมดจะล้มเหลว คุณสามารถทำได้เพียงแค่ว่าเมธอดนั้นจะต้องเป็นแบบทั่วไปทั้งหมดไม่ใช้คุณสมบัติของประเภทที่ไม่ระบุตัวตน
Jon Skeet

2
re object- หรือdynamic; p
Marc Gravell

หากแคสต์ด้วย "เป็น" คุณควรตรวจสอบว่ารายการเป็นโมฆะหรือไม่
Alex

"สามารถ"! = "ต้อง". การใช้objectไม่เหมือนกับการสร้างวิธีการทั่วไปในประเภทนิรนามตามคำตอบของฉัน
Jon Skeet

8

โดยปกติคุณทำสิ่งนี้กับยาชื่อสามัญเช่น:

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

คอมไพเลอร์แล้วควรสรุปเมื่อคุณเรียกT MapEntToObj(query)ไม่ค่อยแน่ใจว่าคุณต้องการทำอะไรในวิธีการนี้ดังนั้นฉันไม่สามารถบอกได้ว่าสิ่งนี้มีประโยชน์หรือไม่ ... ปัญหาคือภายในMapEntToObjคุณยังไม่สามารถตั้งชื่อได้T- คุณสามารถ:

  • เรียกวิธีการทั่วไปอื่น ๆ ด้วย T
  • ใช้การไตร่ตรองในTการทำสิ่งต่างๆ

แต่นอกเหนือจากนั้นมันค่อนข้างยากที่จะจัดการกับประเภทนิรนาม - ไม่น้อยเพราะไม่เปลี่ยนรูป ;-p

เคล็ดลับอีกประการหนึ่ง (เมื่อดึงข้อมูล) คือการส่งตัวเลือกเช่น:

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
ได้เรียนรู้อะไรใหม่ ๆ ไม่รู้ว่าประเภทนิรนามไม่เปลี่ยนรูป! ;)
Annie Lagang

1
@AnneLagang ซึ่งขึ้นอยู่กับคอมไพเลอร์จริง ๆ เนื่องจากสร้างขึ้น ใน VB.NET anon-types สามารถเปลี่ยนแปลงได้
Marc Gravell

1

7

คุณสามารถใช้ยาชื่อสามัญได้ด้วยเคล็ดลับต่อไปนี้ (การแคสต์เป็นประเภทไม่ระบุตัวตน)

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

"ไดนามิก" สามารถใช้เพื่อจุดประสงค์นี้ได้เช่นกัน

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
นี่คือคำตอบที่ใช่! แค่ต้องการความรักมากขึ้น :)
Korayem

2

แทนที่จะส่งประเภทที่ไม่ระบุตัวตนให้ส่งรายการประเภทไดนามิก:

  1. var dynamicResult = anonymousQueryResult.ToList<dynamic>();
  2. ลายเซ็นวิธี: DoSomething(List<dynamic> _dynamicResult)
  3. วิธีการโทร: DoSomething(dynamicResult);
  4. เสร็จแล้ว

ขอบคุณPetar Ivanov !


0

หากคุณทราบว่าผลลัพธ์ของคุณใช้อินเทอร์เฟซบางอย่างคุณสามารถใช้อินเทอร์เฟซเป็นประเภทข้อมูลได้:

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

    }
}

0

ฉันจะใช้IEnumerable<object>เป็นประเภทสำหรับอาร์กิวเมนต์ อย่างไรก็ตามไม่ใช่ผลประโยชน์ที่ดีสำหรับนักแสดงที่ไม่สามารถหลีกเลี่ยงได้ ไชโย

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.