Trả lại kiểu ẩn danh trong C #


99

Tôi có một truy vấn trả về một loại ẩn danh và truy vấn nằm trong một phương thức. Làm thế nào để bạn viết điều này:

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

      return "TheAnonymousType";
    }
}

5
Tại sao bạn muốn trả lại một loại ẩn danh? Làm thế nào bạn có thể sử dụng kết quả đó ở bất kỳ nơi nào khác?
Yuck


5
@Yuck gì xảy ra nếu bạn đang trở về json hoặc một cái gì đó mà c # loại không quan trọng
aw04

10
Tôi không nghĩ câu hỏi này không có lý do. Tôi thực sự cần phải làm điều này vài lần. Nó rõ ràng hơn khi sử dụng khung thực thể và bạn muốn thực hiện truy vấn của mình trong một hàm và sử dụng kết quả ở nhiều nơi. Tôi cần điều này khá thường xuyên khi hiển thị kết quả trên màn hình và sau đó cần sử dụng cùng kết quả trong một báo cáo hoặc khi xuất sang excel. Truy vấn có thể chứa nhiều bộ lọc và như vậy từ giao diện người dùng. bạn không thực sự muốn tạo ra cùng một truy vấn ở một số nơi hoặc bạn có thể dễ dàng nhận ra không đồng bộ khi bạn muốn thêm vào kết quả
Kevbo

Câu trả lời:


94

Bạn không thể.

Bạn chỉ có thể trở lại object, hoặc chứa các đối tượng, ví dụ như IEnumerable<object>, IList<object>vv


51
Hoặc dynamic. Điều đó làm cho nó trở nên dễ dàng hơn một chút để làm việc.
vcsjones

ah ok, vì vậy bạn chỉ có thể sử dụng các kiểu ẩn danh trong một phương thức chứ không thể sử dụng như các giá trị trả về?
frenchie

2
@frenchie: Vâng, chỉ bên trong cơ thể của thành viên. Nếu bạn muốn trả lại - hãy biến nó thành loại nổi tiếng.
abatishchev

11
Sử dụng động không phải là một giải pháp, các trường thuộc loại ẩn danh không công khai, chúng là nội bộ.
Hans Passant

7
@HansPassant Giả sử rằng người gọi nằm trong cùng một assembly, thì nó vẫn (phần nào) hữu ích. Đối với giá trị của nó, các trường là công khai - loại là nội bộ. Tôi thường ở trong trại rằng dù sao thì bạn cũng không nên trả lại kiểu ẩn danh.
vcsjones

42

Bạn có thể trả lại dynamicđể cung cấp cho bạn phiên bản đã kiểm tra thời gian chạy của loại ẩn danh nhưng chỉ trong .NET 4+


29

Trong C # 7, chúng ta có thể sử dụng các bộ giá trị để thực hiện điều này:

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

Bạn có thể cần phải cài đặt System.ValueTuplegói nuget.


27

Bạn không thể trả lại các loại ẩn danh. Bạn có thể tạo ra một mô hình có thể được trả lại? Nếu không, bạn phải sử dụng một object.

Đây là một bài báo được viết bởi Jon Skeet về chủ đề

Mã từ bài báo:

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

Hoặc, đây là một bài báo tương tự khác

Hoặc, khi những người khác đang bình luận, bạn có thể sử dụng dynamic


8
Tất nhiên tôi có thể tạo một kiểu; Tôi đã tìm cách tránh làm điều này.
frenchie

liên kết đầu tiên đã chết, bạn sẽ không biết nó có được chuyển đi nơi khác không?
Rémi

17

Bạn có thể sử dụng lớp Tuple để thay thế cho các kiểu ẩn danh khi việc trả lại là cần thiết:

Lưu ý: Tuple có thể có tối đa 8 tham số.

return Tuple.Create(variable1, variable2);

Hoặc, ví dụ từ bài đăng gốc:

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

Trình biên dịch C # là trình biên dịch hai pha. Trong giai đoạn đầu, nó chỉ kiểm tra không gian tên, phân cấp lớp, chữ ký phương thức, v.v. Các phần thân phương thức chỉ được biên dịch trong giai đoạn thứ hai.

Các kiểu ẩn danh không được xác định cho đến khi phần thân phương thức được biên dịch.

Vì vậy, trình biên dịch không có cách nào xác định kiểu trả về của phương thức trong giai đoạn đầu.

Đó là lý do tại sao các kiểu ẩn danh không thể được sử dụng làm kiểu trả về.

Như những người khác đã đề xuất nếu bạn đang sử dụng .net 4.0 hoặc grater, bạn có thể sử dụng Dynamic.

Nếu tôi là bạn, tôi có thể sẽ tạo một kiểu và trả về kiểu đó từ phương thức. Bằng cách đó, các lập trình viên tương lai sẽ dễ dàng duy trì mã của bạn và dễ đọc hơn.


8

Ba lựa chọn:

Lựa chọn 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;
   } 
}

Lựa chọn 2:

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

bạn có thể lặp lại nó dưới dạng đối tượng

Tùy chọn 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
   } 
}

và bạn sẽ có thể lặp lại nó như một đối tượng động và truy cập trực tiếp vào các thuộc tính của chúng


3

Bạn có thể trả về danh sách các đối tượng trong trường hợp này.

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

      return TheQueryFromDB ;
    }
}

3

Sử dụng C # 7.0, chúng tôi vẫn không thể trả về các loại ẩn danh nhưng chúng tôi có hỗ trợ các loại tuple và do đó chúng tôi có thể trả về một tập hợp tuple( System.ValueTuple<T1,T2>trong trường hợp này). Hiện tại Tuple types không được hỗ trợ trong cây biểu thức và bạn cần tải dữ liệu vào bộ nhớ.

Phiên bản ngắn nhất của mã bạn muốn có thể trông giống như sau:

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

Hoặc sử dụng cú pháp Linq thông thạo:

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

Sử dụng C # 7.1, chúng ta có thể bỏ qua các tên thuộc tính của tuple và chúng sẽ được suy ra từ việc khởi tạo tuple giống như nó hoạt động với các kiểu ẩn danh:

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

Tạo lớp của riêng bạn và truy vấn cho nó là giải pháp tốt nhất mà tôi biết. Theo tôi biết, bạn không thể sử dụng các giá trị trả về kiểu ẩn danh trong một phương thức khác, vì nó sẽ không chỉ được nhận dạng. Tuy nhiên, chúng có thể được sử dụng trong cùng một phương pháp. Tôi đã sử dụng để trả về chúng dưới dạng IQueryablehoặc IEnumerable, mặc dù nó vẫn không cho bạn thấy những gì bên trong biến kiểu ẩn danh.

Tôi đã gặp phải một thứ như thế này trước đây khi tôi đang cố gắng cấu trúc lại một số mã, bạn có thể kiểm tra tại đây: Cấu trúc lại và tạo các phương thức riêng biệt


2

Với sự phản ánh.

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

Mẫu vật:

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

Đầu ra:

test2

1

Bạn chỉ có thể sử dụng từ khóa động,

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

Nhưng với từ khóa loại động, bạn sẽ mất an toàn thời gian biên dịch, IDE IntelliSense, v.v.


0

Một tùy chọn khác có thể là sử dụng automapper: Bạn sẽ chuyển đổi sang bất kỳ loại nào từ đối tượng trả về ẩn danh của mình khi các thuộc tính công cộng dài phù hợp. Các điểm chính là, trả về đối tượng, sử dụng linq và autommaper. (hoặc sử dụng ý tưởng tương tự trả về json được tuần tự hóa, v.v. hoặc sử dụng phản ánh ..)

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

Bây giờ đặc biệt là với các chức năng cục bộ, nhưng bạn luôn có thể làm điều đó bằng cách chuyển một đại biểu tạo kiểu ẩn danh.

Vì vậy, nếu mục tiêu của bạn là chạy các logic khác nhau trên cùng một nguồn và có thể kết hợp các kết quả thành một danh sách. Không chắc chắn sắc thái này còn thiếu để đáp ứng mục tiêu đã nêu, nhưng miễn là bạn trả về một Tvà chuyển một đại diện để thực hiện T, bạn có thể trả về một kiểu ẩn danh từ một hàm.

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

0

Thực sự có thể trả về một kiểu ẩn danh từ một phương thức trong một trường hợp sử dụng cụ thể. Chúng ta hãy có một cái nhìn!

Với C # 7, có thể trả về các kiểu ẩn danh từ một phương thức, mặc dù nó có một chút hạn chế. Chúng ta sẽ sử dụng một tính năng ngôn ngữ mới được gọi là hàm cục bộ cùng với một thủ thuật chuyển hướng (một lớp hướng dẫn khác có thể giải quyết bất kỳ thách thức lập trình nào, phải không?).

Đây là một trường hợp sử dụng mà tôi đã xác định gần đây. Tôi muốn ghi lại tất cả các giá trị cấu hình sau khi tôi đã tải chúng từ AppSettings. Tại sao? Bởi vì có một số logic xung quanh các giá trị bị thiếu sẽ hoàn nguyên về giá trị mặc định, một số phân tích cú pháp, v.v. Một cách dễ dàng để ghi nhật ký các giá trị sau khi áp dụng logic là đặt tất cả chúng vào một lớp và tuần tự hóa nó thành một tệp log (sử dụng log4net). Tôi cũng muốn gói gọn logic phức tạp của việc xử lý các cài đặt và tách biệt điều đó khỏi bất cứ điều gì tôi cần làm với chúng. Tất cả mà không cần tạo một lớp được đặt tên tồn tại chỉ cho một mục đích sử dụng!

Hãy xem cách giải quyết vấn đề này bằng cách sử dụng một hàm cục bộ tạo một kiểu ẩn danh.

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

Tôi đã thành công trong việc xây dựng một lớp ẩn danh và cũng đóng gói logic xử lý việc quản lý cài đặt phức tạp, tất cả bên trong CreateHttpClientvà bên trong "biểu thức" của riêng nó. Đây có thể không phải là chính xác những gì OP muốn nhưng đó là một cách tiếp cận nhẹ với các kiểu ẩn danh hiện có thể thực hiện được trong C # hiện đại.

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.