Mengembalikan tipe anonim di C #

100

Saya memiliki kueri yang mengembalikan tipe anonim dan kueri dalam metode. Bagaimana Anda menulis ini:

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

      return "TheAnonymousType";
    }
}
frenchie
sumber
5
Mengapa Anda ingin mengembalikan tipe anonim? Bagaimana Anda bisa menggunakan hasil itu di tempat lain?
Yuck
5
@Yuck bagaimana jika Anda mengembalikan json atau sesuatu di mana tipe c # tidak masalah
aw04
10
Saya tidak berpikir pertanyaan ini keluar dari alasan. Saya sebenarnya perlu melakukan ini beberapa kali. Ini lebih jelas ketika menggunakan kerangka entitas dan Anda ingin melakukan kueri Anda dalam satu fungsi dan menggunakan hasilnya di beberapa tempat. Saya membutuhkan ini cukup sering saat menampilkan hasil di layar dan kemudian perlu menggunakan hasil yang sama dalam laporan atau saat mengekspor ke excel. Kueri mungkin berisi banyak filter dan semacamnya dari UI. Anda tidak benar-benar ingin membuat kueri yang sama di beberapa tempat atau Anda dapat dengan mudah keluar dari sinkronisasi saat Anda ingin menambahkan hasil
Kevbo
1
Kemungkinan duplikat dari Apakah ada cara untuk mengembalikan Jenis Anonim dari metode?
HaveNoDisplayName

Jawaban:

94

Tidak boleh.

Anda hanya dapat kembali object, atau wadah benda, misalnya IEnumerable<object>, IList<object>, dll

abatishchev
sumber
51
Atau dynamic. Itu membuatnya sedikit lebih mudah untuk dikerjakan.
vcsjones
ah ok, jadi Anda hanya bisa menggunakan tipe anonim dalam sebuah metode tapi tidak sebagai nilai yang dikembalikan?
frenchie
2
@frenchie: Ya, hanya di dalam tubuh anggota. Jika Anda ingin mengembalikannya - buatlah itu menjadi tipe yang terkenal.
abatishchev
11
Menggunakan dinamis bukanlah solusi, bidang jenis anonim tidak bersifat publik, melainkan internal.
Hans Passant
7
@HansPassant Dengan asumsi bahwa pemanggil berada dalam rakitan yang sama, maka masih (agak) berguna. Untuk apa nilainya, bidang ini bersifat publik - tipenya internal. Saya biasanya di kamp sehingga Anda tidak boleh mengembalikan tipe anonim.
vcsjones
42

Anda dapat mengembalikan dynamicyang akan memberi Anda versi runtime dari tipe anonim tetapi hanya di .NET 4+

Tidak dicintai
sumber
30

Di C # 7 kita dapat menggunakan tupel untuk mencapai ini:

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

Anda mungkin perlu menginstal System.ValueTuplepaket nuget.

Rosdi Kasim
sumber
27

Anda tidak dapat mengembalikan tipe anonim. Bisakah Anda membuat model yang bisa dikembalikan? Jika tidak, Anda harus menggunakan file object.

Berikut adalah artikel yang ditulis oleh Jon Skeet tentang masalah ini

Kode dari artikel:

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

Atau, ini artikel serupa lainnya

Atau, saat orang lain berkomentar, Anda dapat menggunakan dynamic

Justin Pihony
sumber
8
Tentu saja saya bisa membuat tipe; Saya ingin menghindari melakukan ini.
frenchie
tautan pertama sudah mati, bukankah Anda kebetulan tahu jika itu telah ditransfer ke tempat lain?
Rémi
17

Anda dapat menggunakan kelas Tuple sebagai pengganti tipe anonim saat pengembalian diperlukan:

Catatan: Tuple dapat memiliki hingga 8 parameter.

return Tuple.Create(variable1, variable2);

Atau, untuk contoh dari postingan aslinya:

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

craigrs84
sumber
10

Kompilator C # adalah kompilator dua fase. Pada tahap pertama ini hanya memeriksa ruang nama, hierarki kelas, tanda tangan Metode, dll. Badan metode dikompilasi hanya selama tahap kedua.

Jenis anonim tidak ditentukan hingga badan metode dikompilasi.

Jadi kompilator tidak memiliki cara untuk menentukan tipe kembalian metode selama fase pertama.

Itulah alasan mengapa tipe anonim tidak bisa digunakan sebagai tipe kembalian.

Seperti yang disarankan orang lain jika Anda menggunakan .net 4.0 atau parutan, Anda dapat menggunakan Dynamic.

Jika saya jadi Anda, saya mungkin akan membuat tipe dan mengembalikan tipe itu dari metode. Dengan begitu, mudah bagi programmer masa depan yang memelihara kode Anda dan lebih mudah dibaca.

Sandeep
sumber
8

Tiga opsi:

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

Pilihan 2:

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

Anda dapat mengulanginya sebagai objek

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

dan Anda akan dapat mengulanginya sebagai objek dinamis dan mengakses propertinya secara langsung

Ivo
sumber
3

Anda dapat mengembalikan daftar objek dalam kasus ini.

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

      return TheQueryFromDB ;
    }
}
Chuck Norris
sumber
3

Menggunakan C # 7.0 kami masih tidak bisa mengembalikan tipe anonim tapi kami memiliki dukungan tipe tuple dan dengan demikian kami dapat mengembalikan koleksi tuple( System.ValueTuple<T1,T2>dalam kasus ini). Saat Tuple types ini tidak didukung di pohon ekspresi dan Anda perlu memuat data ke dalam memori.

Versi terpendek dari kode yang Anda inginkan mungkin terlihat seperti ini:

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

Atau menggunakan sintaks Linq yang lancar:

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

Menggunakan C # 7.1 kita dapat menghilangkan nama properti tupel dan mereka akan disimpulkan dari inisialisasi tupel seperti ini bekerja dengan tipe anonim:

select (data.SomeInt, data.SomeObject)
// or
Select(data => (data.SomeInt, data.SomeObject))
AlbertK
sumber
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;}
}

Membuat kelas Anda sendiri dan menanyakannya adalah solusi terbaik yang saya tahu.Seperti yang saya tahu Anda tidak dapat menggunakan nilai kembalian tipe anonim dalam metode lain, karena itu tidak hanya akan dikenali.Namun, mereka dapat digunakan dengan cara yang sama metode. Saya biasa mengembalikannya sebagai IQueryableatau IEnumerable, meskipun masih tidak memungkinkan Anda melihat apa yang ada di dalam variabel tipe anonim.

Saya mengalami sesuatu seperti ini sebelumnya ketika saya mencoba memfaktor ulang beberapa kode, Anda dapat memeriksanya di sini: Refactoring dan membuat metode terpisah

Bastardo
sumber
2

Dengan refleksi.

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

Sampel:

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

Keluaran:

test2
Mehmet YAŞAR
sumber
1

Anda hanya dapat menggunakan kata kunci dinamis,

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

Tetapi dengan kata kunci tipe dinamis Anda akan kehilangan keamanan waktu kompilasi, IDE IntelliSense dll.

Imran Jalali
sumber
0

Opsi lain dapat menggunakan automapper: Anda akan mengonversi ke jenis apa pun dari objek anonim Anda yang dikembalikan selama properti publik cocok. Poin utamanya adalah, mengembalikan objek, menggunakan linq dan autommaper. (atau gunakan ide serupa untuk mengembalikan serialized json, dll. atau gunakan refleksi ..)

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

}
alhpe
sumber
0

Sekarang dengan fungsi lokal khususnya, tetapi Anda selalu dapat melakukannya dengan mengirimkan delegasi yang membuat tipe anonim.

Jadi, jika tujuan Anda adalah menjalankan logika yang berbeda pada sumber yang sama, dan dapat menggabungkan hasil ke dalam satu daftar. Tidak yakin nuansa apa yang hilang ini untuk memenuhi tujuan yang dinyatakan, tetapi selama Anda mengembalikan Tdan mengirimkan delegasi untuk membuatnya T, Anda dapat mengembalikan tipe anonim dari suatu fungsi.

// 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);
}
Maslow
sumber
0

Sebenarnya dimungkinkan untuk mengembalikan tipe anonim dari metode dalam kasus penggunaan tertentu. Mari kita lihat!

Dengan C # 7 dimungkinkan untuk mengembalikan tipe anonim dari sebuah metode, meskipun itu datang dengan sedikit kendala. Kami akan menggunakan fitur bahasa baru yang disebut fungsi lokal bersama dengan trik tipuan (lapisan tipuan lain dapat menyelesaikan tantangan pemrograman apa pun, bukan?).

Inilah kasus penggunaan yang baru-baru ini saya identifikasi. Saya ingin mencatat semua nilai konfigurasi setelah saya memuatnya dari AppSettings. Mengapa? Karena ada beberapa logika di sekitar nilai yang hilang yang kembali ke nilai default, beberapa parsing, dan sebagainya. Cara mudah untuk mencatat nilai setelah menerapkan logika adalah dengan meletakkan semuanya di kelas dan membuat serial ke file log (menggunakan log4net). Saya juga ingin merangkum logika kompleks dalam menangani pengaturan dan memisahkannya dari apa pun yang perlu saya lakukan dengannya. Semua tanpa membuat kelas bernama yang ada hanya untuk sekali penggunaan!

Mari kita lihat bagaimana menyelesaikan ini menggunakan fungsi lokal yang membuat tipe anonim.

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

Saya telah berhasil membangun kelas anonim dan juga merangkum logika berurusan dengan manajemen pengaturan yang kompleks, semua di dalam CreateHttpClientdan di dalam "ekspresi" -nya sendiri. Ini mungkin tidak persis seperti yang diinginkan OP tetapi ini adalah pendekatan ringan dengan tipe anonim yang saat ini dimungkinkan dalam C # modern.

Daniel Lidström
sumber