การส่งคืนประเภทที่ไม่ระบุตัวตนใน C #


103

ฉันมีแบบสอบถามที่ส่งคืนประเภทที่ไม่ระบุตัวตนและแบบสอบถามอยู่ในวิธีการ คุณเขียนสิ่งนี้อย่างไร:

public "TheAnonymousType" TheMethod(SomeParameter)
{
  using (MyDC TheDC = new MyDC())
  {
     var TheQueryFromDB = (....
                           select new { SomeVariable = ....,
                                        AnotherVariable = ....}
                           ).ToList();

      return "TheAnonymousType";
    }
}

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


6
@ แย่มากถ้าคุณกลับ json หรือของที่ประเภท c # ไม่สำคัญ
aw04

10
ฉันไม่คิดว่าคำถามนี้ไม่มีเหตุผล ฉันต้องทำหลายครั้งแล้ว ชัดเจนยิ่งขึ้นเมื่อใช้กรอบงานเอนทิตีและคุณต้องการทำแบบสอบถามของคุณในฟังก์ชันเดียวและใช้ผลลัพธ์ในหลาย ๆ ที่ ฉันต้องการสิ่งนี้ค่อนข้างบ่อยเมื่อแสดงผลลัพธ์บนหน้าจอจากนั้นจำเป็นต้องใช้ผลลัพธ์เดียวกันในรายงานหรือเมื่อส่งออกไปยัง excel คำค้นหาอาจมีตัวกรองจำนวนมากและจาก UI คุณไม่ต้องการสร้างแบบสอบถามเดียวกันในหลาย ๆ ที่หรือคุณสามารถออกจากการซิงค์ได้อย่างง่ายดายเมื่อคุณต้องการเพิ่มลงในผลลัพธ์
Kevbo

คำตอบ:


95

คุณทำไม่ได้

คุณสามารถกลับมาobjectหรือภาชนะของวัตถุเช่นIEnumerable<object>, IList<object>ฯลฯ


52
หรือdynamic. นั่นทำให้การทำงานกับมันง่ายขึ้นเล็กน้อย
vcsjones

อ่าตกลงดังนั้นคุณสามารถใช้ได้เฉพาะประเภทที่ไม่ระบุตัวตนภายในเมธอด แต่ใช้เป็นค่าส่งคืนไม่ได้?
frenchie

2
@frenchie: ใช่ภายในร่างกายของสมาชิกเท่านั้น หากต้องการคืน - ทำให้เป็นประเภทที่รู้จักกันดี
abatishchev

11
การใช้ไดนามิกไม่ใช่วิธีแก้ปัญหาฟิลด์ของประเภทที่ไม่ระบุตัวตนไม่ได้เป็นแบบสาธารณะ แต่เป็นแบบภายใน
Hans Passant

8
@HansPassant สมมติว่าผู้โทรอยู่ในแอสเซมบลีเดียวกันแล้วก็ยังมีประโยชน์ (บ้าง) สำหรับสิ่งที่คุ้มค่าฟิลด์นี้เป็นแบบสาธารณะ - ประเภทเป็นแบบภายใน โดยทั่วไปฉันอยู่ในค่ายที่คุณไม่ควรส่งคืนประเภทที่ไม่ระบุตัวตนอยู่แล้ว
vcsjones


34

ใน C # 7 เราสามารถใช้ tuples เพื่อทำสิ่งนี้ให้สำเร็จ:

public List<(int SomeVariable, string AnotherVariable)> TheMethod(SomeParameter)
{
  using (MyDC TheDC = new MyDC())
  {
     var TheQueryFromDB = (....
                       select new { SomeVariable = ....,
                                    AnotherVariable = ....}
                       ).ToList();

      return TheQueryFromDB
                .Select(s => (
                     SomeVariable = s.SomeVariable, 
                     AnotherVariable = s.AnotherVariable))
                 .ToList();
  }
}

คุณอาจต้องติดตั้งSystem.ValueTupleแพ็คเกจ nuget


นี่คือสิ่งที่ฉันกำลังมองหาดูเหมือนเป็นประเภท JS
Fabrice T

28

คุณไม่สามารถส่งคืนประเภทที่ไม่ระบุตัวตน คุณสามารถสร้างแบบจำลองที่สามารถส่งคืนได้หรือไม่? มิฉะนั้นคุณต้องใช้objectไฟล์.

นี่คือบทความที่เขียนโดย Jon Skeet ในหัวข้อนี้

รหัสจากบทความ:

using System;

static class GrottyHacks
{
    internal static T Cast<T>(object target, T example)
    {
        return (T) target;
    }
}

class CheesecakeFactory
{
    static object CreateCheesecake()
    {
        return new { Fruit="Strawberry", Topping="Chocolate" };
    }

    static void Main()
    {
        object weaklyTyped = CreateCheesecake();
        var stronglyTyped = GrottyHacks.Cast(weaklyTyped,
            new { Fruit="", Topping="" });

        Console.WriteLine("Cheesecake: {0} ({1})",
            stronglyTyped.Fruit, stronglyTyped.Topping);            
    }
}

หรือนี่คือบทความอื่นที่คล้ายกัน

หรือในขณะที่คนอื่นแสดงความคิดเห็นคุณสามารถใช้ dynamic


8
แน่นอนฉันสามารถสร้างประเภท; ฉันต้องการหลีกเลี่ยงการทำเช่นนี้
frenchie

ลิงค์แรกตายแล้วคุณจะไม่ทราบว่าถูกโอนไปที่อื่นหรือไม่?
Rémi

18

คุณสามารถใช้คลาส Tuple แทนประเภทที่ไม่ระบุตัวตนได้เมื่อจำเป็นต้องส่งคืน:

หมายเหตุ: Tuple สามารถมีได้ถึง 8 พารามิเตอร์

return Tuple.Create(variable1, variable2);

หรือตัวอย่างจากโพสต์ต้นฉบับ:

public List<Tuple<SomeType, AnotherType>> TheMethod(SomeParameter)
{
  using (MyDC TheDC = new MyDC())
  {
     var TheQueryFromDB = (....
                           select Tuple.Create(..., ...)
                           ).ToList();

      return TheQueryFromDB.ToList();
    }
}

http://msdn.microsoft.com/en-us/library/system.tuple(v=vs.110).aspx


10

คอมไพเลอร์ C # เป็นคอมไพเลอร์สองเฟส ในช่วงแรกจะตรวจสอบเนมสเปซลำดับชั้นลายเซ็นของเมธอด ฯลฯ เนื้อหาของเมธอดจะถูกคอมไพล์ในช่วงที่สองเท่านั้น

ไม่ระบุชนิดที่ไม่ระบุตัวตนจนกว่าจะคอมไพล์เนื้อหาวิธีการ

ดังนั้นคอมไพลเลอร์จึงไม่มีวิธีกำหนดชนิดการส่งคืนของวิธีการในช่วงแรก

นั่นคือเหตุผลที่ไม่สามารถใช้ anonymous types เป็น return type ได้

ตามที่คนอื่นแนะนำหากคุณใช้. net 4.0 หรือเครื่องขูดคุณสามารถใช้ Dynamic .

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


8

สามตัวเลือก:

ตัวเลือกที่ 1:

public class TheRepresentativeType {
    public ... SomeVariable {get;set;}
    public ... AnotherVariable {get;set;}
}

public IEnumerable<TheRepresentativeType> TheMethod(SomeParameter)
{
   using (MyDC TheDC = new MyDC())
   {
     var TheQueryFromDB = (....
                           select new TheRepresentativeType{ SomeVariable = ....,
                                        AnotherVariable = ....}
                           ).ToList();

     return TheQueryFromDB;
   } 
}

ทางเลือกที่ 2:

public IEnumerable TheMethod(SomeParameter)
{
   using (MyDC TheDC = new MyDC())
   {
     var TheQueryFromDB = (....
                           select new TheRepresentativeType{ SomeVariable = ....,
                                        AnotherVariable = ....}
                           ).ToList();
     return TheQueryFromDB;
   } 
}

คุณสามารถทำซ้ำเป็นวัตถุได้

ทางเลือกที่ 3:

public IEnumerable<dynamic> TheMethod(SomeParameter)
{
   using (MyDC TheDC = new MyDC())
   {
     var TheQueryFromDB = (....
                           select new TheRepresentativeType{ SomeVariable = ....,
                                        AnotherVariable = ....}
                           ).ToList();

     return TheQueryFromDB; //You may need to call .Cast<dynamic>(), but I'm not sure
   } 
}

และคุณจะสามารถทำซ้ำเป็นวัตถุไดนามิกและเข้าถึงคุณสมบัติของมันได้โดยตรง


3

คุณสามารถส่งคืนรายการวัตถุในกรณีนี้

public List<object> TheMethod(SomeParameter)
{
  using (MyDC TheDC = new MyDC())
  {
     var TheQueryFromDB = (....
                           select new { SomeVariable = ....,
                                        AnotherVariable = ....}
                           ).ToList();

      return TheQueryFromDB ;
    }
}

3

การใช้C # 7.0เรายังคงไม่สามารถส่งคืนประเภทที่ไม่ระบุตัวตนได้ แต่เรามีการสนับสนุนประเภททูเปิลดังนั้นเราจึงสามารถส่งคืนคอลเล็กชันtuple( System.ValueTuple<T1,T2>ในกรณีนี้) ขณะนี้Tuple types ยังไม่รองรับแผนภูมินิพจน์และคุณต้องโหลดข้อมูลลงในหน่วยความจำ

โค้ดเวอร์ชันสั้นที่สุดที่คุณต้องการอาจมีลักษณะดังนี้:

public IEnumerable<(int SomeVariable, object AnotherVariable)> TheMethod()
{
    ...

    return (from data in TheDC.Data
        select new { data.SomeInt, data.SomeObject }).ToList()
        .Select(data => (SomeVariable: data.SomeInt, AnotherVariable: data.SomeObject))
}

หรือใช้ไวยากรณ์ Linq ที่คล่องแคล่ว:

return TheDC.Data
    .Select(data => new {SomeVariable: data.SomeInt, AnotherVariable: data.SomeObject})
    .ToList();
    .Select(data => (SomeVariable: data.SomeInt, AnotherVariable: data.SomeObject))

การใช้C # 7.1เราสามารถละเว้นชื่อคุณสมบัติของทูเปิลได้และจะถูกอนุมานจากการเริ่มต้นทูเพิลเช่นเดียวกับที่ทำงานกับประเภทที่ไม่ระบุตัวตน:

select (data.SomeInt, data.SomeObject)
// or
Select(data => (data.SomeInt, data.SomeObject))

2
public List<SomeClass> TheMethod(SomeParameter)
{
  using (MyDC TheDC = new MyDC())
  {
     var TheQueryFromDB = (....
                           select new SomeClass{ SomeVariable = ....,
                                        AnotherVariable = ....}
                           ).ToList();

      return TheQueryFromDB.ToList();
    }
}

public class SomeClass{
   public string SomeVariable{get;set}
   public string AnotherVariable{get;set;}
}

การสร้างคลาสของคุณเองและการสอบถามเป็นวิธีแก้ปัญหาที่ดีที่สุดเท่าที่ฉันรู้ว่าคุณไม่สามารถใช้ค่าส่งคืนประเภทที่ไม่ระบุชื่อในวิธีอื่นได้เพราะจะไม่ได้รับการยอมรับเพียงอย่างเดียวอย่างไรก็ตามสามารถใช้ได้ในลักษณะเดียวกัน วิธี. ฉันเคยส่งคืนเป็นIQueryableหรือIEnumerableแม้ว่าจะยังไม่ให้คุณเห็นสิ่งที่อยู่ภายในตัวแปรประเภทที่ไม่ระบุตัวตน

ก่อนหน้านี้ฉันเจออะไรแบบนี้ในขณะที่พยายามรีแฟคเตอร์โค้ดคุณสามารถตรวจสอบได้ที่นี่: การสร้างโครงสร้างใหม่และสร้างวิธีการแยกต่างหาก


2

ด้วยการสะท้อน.

public object tst() {
    var a = new {
        prop1 = "test1",
        prop2 = "test2"
    };

    return a;
}


public string tst2(object anonymousObject, string propName) {
    return anonymousObject.GetType().GetProperties()
        .Where(w => w.Name == propName)
        .Select(s => s.GetValue(anonymousObject))
        .FirstOrDefault().ToString();
}

ตัวอย่าง:

object a = tst();
var val = tst2(a, "prop2");

เอาท์พุต:

test2

1

คุณสามารถใช้ได้เฉพาะคำหลักแบบไดนามิก

   dynamic obj = GetAnonymousType();

   Console.WriteLine(obj.Name);
   Console.WriteLine(obj.LastName);
   Console.WriteLine(obj.Age); 


   public static dynamic GetAnonymousType()
   {
       return new { Name = "John", LastName = "Smith", Age=42};
   }

แต่ด้วยคำหลักประเภทไดนามิกคุณจะสูญเสียความปลอดภัยของเวลาในการคอมไพล์ IDE IntelliSense ฯลฯ ...


1

เป็นไปได้จริงที่จะส่งคืนประเภทที่ไม่ระบุตัวตนจากวิธีการในกรณีการใช้งานเฉพาะ มาดูกัน!

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

นี่คือกรณีการใช้งานที่ฉันเพิ่งระบุ ฉันต้องการบันทึกค่าการกำหนดค่าทั้งหมดหลังจากที่ฉันโหลดจากAppSettingsไฟล์. ทำไม? เนื่องจากมีตรรกะบางอย่างเกี่ยวกับค่าที่หายไปซึ่งจะเปลี่ยนกลับเป็นค่าเริ่มต้นการแยกวิเคราะห์และอื่น ๆ วิธีง่ายๆในการบันทึกค่าหลังจากใช้ตรรกะคือการใส่ค่าทั้งหมดลงในคลาสและทำให้เป็นอนุกรมลงในไฟล์บันทึก (โดยใช้ log4net) ฉันยังต้องการสรุปตรรกะที่ซับซ้อนในการจัดการกับการตั้งค่าและแยกสิ่งนั้นออกจากสิ่งที่ฉันต้องทำด้วย ทั้งหมดนี้ไม่ต้องสร้างคลาสที่มีชื่อซึ่งมีอยู่สำหรับการใช้งานเพียงครั้งเดียว!

มาดูวิธีแก้ปัญหานี้โดยใช้ฟังก์ชันท้องถิ่นที่สร้างประเภทที่ไม่ระบุตัวตน

public static HttpClient CreateHttpClient()
{
    // I deal with configuration values in this slightly convoluted way.
    // The benefit is encapsulation of logic and we do not need to
    // create a class, as we can use an anonymous class.
    // The result resembles an expression statement that
    // returns a value (similar to expressions in F#)
    var config = Invoke(() =>
    {
        // slightly complex logic with default value
        // in case of missing configuration value
        // (this is what I want to encapsulate)
        int? acquireTokenTimeoutSeconds = null;
        if (int.TryParse(ConfigurationManager.AppSettings["AcquireTokenTimeoutSeconds"], out int i))
        {
            acquireTokenTimeoutSeconds = i;
        }

        // more complex logic surrounding configuration values ...

        // construct the aggregate configuration class as an anonymous type!
        var c = new
        {
            AcquireTokenTimeoutSeconds =
                acquireTokenTimeoutSeconds ?? DefaultAcquireTokenTimeoutSeconds,
            // ... more properties
        };

        // log the whole object for monitoring purposes
        // (this is also a reason I want encapsulation)
        Log.InfoFormat("Config={0}", c);
        return c;
    });

    // use this configuration in any way necessary...
    // the rest of the method concerns only the factory,
    // i.e. creating the HttpClient with whatever configuration
    // in my case this:
    return new HttpClient(...);

    // local function that enables the above expression
    T Invoke<T>(Func<T> func) => func.Invoke();
}

ฉันประสบความสำเร็จในการสร้างคลาสที่ไม่ระบุตัวตนและยังสรุปตรรกะของการจัดการกับการจัดการการตั้งค่าที่ซับซ้อนทั้งภายในCreateHttpClientและภายใน "นิพจน์" ของมันเอง นี่อาจไม่ใช่สิ่งที่ OP ต้องการอย่างแน่นอน แต่เป็นแนวทางที่มีน้ำหนักเบาพร้อมประเภทนิรนามที่เป็นไปได้ใน C # สมัยใหม่


0

อีกทางเลือกหนึ่งอาจใช้ automapper: คุณจะแปลงเป็นประเภทใดก็ได้จากวัตถุที่ส่งคืนที่ไม่ระบุชื่อของคุณเมื่อคุณสมบัติสาธารณะที่ยาวตรงกัน ประเด็นสำคัญคือการส่งคืนวัตถุใช้ linq และ autommaper (หรือใช้แนวคิดที่คล้ายกันที่ส่งคืน json แบบอนุกรม ฯลฯ หรือใช้การสะท้อนกลับ .. )

using System.Linq;
using System.Reflection;
using AutoMapper;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;

namespace UnitTestProject1
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            var data = GetData();

            var firts = data.First();

            var info = firts.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).First(p => p.Name == "Name");
            var value = info.GetValue(firts);

            Assert.AreEqual(value, "One");
        }


        [TestMethod]
        public void TestMethod2()
        {
            var data = GetData();

            var config = new MapperConfiguration(cfg => cfg.CreateMissingTypeMaps = true);
            var mapper = config.CreateMapper();

            var users = data.Select(mapper.Map<User>).ToArray();

            var firts = users.First();

            Assert.AreEqual(firts.Name, "One");

        }

        [TestMethod]
        public void TestMethod3()
        {
            var data = GetJData();


            var users = JsonConvert.DeserializeObject<User[]>(data);

            var firts = users.First();

            Assert.AreEqual(firts.Name, "One");

        }

        private object[] GetData()
        {

            return new[] { new { Id = 1, Name = "One" }, new { Id = 2, Name = "Two" } };
        }

        private string GetJData()
        {

            return JsonConvert.SerializeObject(new []{ new { Id = 1, Name = "One" }, new { Id = 2, Name = "Two" } }, Formatting.None);
        }

        public class User
        {
            public int Id { get; set; }
            public string Name { get; set; }
        }
    }

}

0

ขณะนี้มีฟังก์ชั่นเฉพาะที่โดยเฉพาะ แต่คุณสามารถทำได้โดยส่งผ่านตัวแทนที่ทำให้เป็นประเภทที่ไม่ระบุตัวตน

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

// returning an anonymous type
// look mom no casting
void LookMyChildReturnsAnAnonICanConsume()
{
    // if C# had first class functions you could do
    // var anonyFunc = (name:string,id:int) => new {Name=name,Id=id};
    var items = new[] { new { Item1 = "hello", Item2 = 3 } };
    var itemsProjection =items.Select(x => SomeLogic(x.Item1, x.Item2, (y, i) => new { Word = y, Count = i} ));
    // same projection = same type
    var otherSourceProjection = SomeOtherSource((y,i) => new {Word=y,Count=i});
    var q =
        from anony1 in itemsProjection
        join anony2 in otherSourceProjection
            on anony1.Word equals anony2.Word
        select new {anony1.Word,Source1Count=anony1.Count,Source2Count=anony2.Count};
    var togetherForever = itemsProjection.Concat(otherSourceProjection).ToList();
}

T SomeLogic<T>(string item1, int item2, Func<string,int,T> f){
    return f(item1,item2);
}
IEnumerable<T> SomeOtherSource<T>(Func<string,int,T> f){
    var dbValues = new []{Tuple.Create("hello",1), Tuple.Create("bye",2)};
    foreach(var x in dbValues)
        yield return f(x.Item1,x.Item2);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.