Возврат анонимного типа в C #


102

У меня есть запрос, который возвращает анонимный тип, и запрос находится в методе. Как это написать:

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

      return "TheAnonymousType";
    }
}

5
Зачем вам возвращать анонимный тип? Как вы могли использовать этот результат где-нибудь еще?
Yuck


5
@ Да ну, что, если вы возвращаете json или что-то еще, где тип C # не имеет значения
aw04

10
Я не думаю, что этот вопрос напрасен. Мне действительно приходилось делать это несколько раз. Это более очевидно при использовании структуры сущностей, и вы хотите выполнить свой запрос в одной функции и использовать результаты в нескольких местах. Мне это нужно довольно часто при отображении результатов на экране, а затем при необходимости использовать те же результаты в отчете или при экспорте в Excel. Запрос может содержать множество фильтров и тому подобное из пользовательского интерфейса. на самом деле вы не хотите создавать один и тот же запрос в нескольких местах, или вы можете легко выйти из синхронизации, когда захотите добавить к результатам
Кевбо,

Ответы:


95

Вы не можете.

Вы можете только вернуть objectили контейнер с объектами, например IEnumerable<object>, IList<object>и т. Д.


52
Или dynamic. Это значительно упрощает работу.
vcsjones 09

ах, хорошо, значит, вы можете использовать только анонимные типы в методе, но не в качестве возвращаемых значений?
frenchie

2
@frenchie: Да, только внутри тела участника. Если хотите вернуть - сделайте известный тип.
Абатищев 09

11
Использование dynamic не является решением, поля анонимного типа не являются общедоступными, они являются внутренними.
Ханс Пассан

7
@HansPassant Предполагая, что вызывающий объект находится в той же сборке, это все еще (в некоторой степени) полезно. Как бы то ни было, поля общедоступные - тип внутренний. Я вообще нахожусь в лагере, что вы все равно не должны возвращать анонимный тип.
vcsjones 09

42

Вы можете вернуть, dynamicчто даст вам версию анонимного типа, проверенную во время выполнения, но только в .NET 4+


31

В C # 7 для этого можно использовать кортежи:

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.


28

Вы не можете возвращать анонимные типы. Можете ли вы создать модель, которую можно вернуть? В противном случае вы должны использовать object.

Вот статья, написанная Джоном Скитом на эту тему.

Код из статьи:

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

первая ссылка мертва, разве вы не знаете, перенесена ли она в другое место?
Реми

17

Вы можете использовать класс 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 # - это двухфазный компилятор. На первом этапе он просто проверяет пространства имен, иерархии классов, сигнатуры методов и т. Д. Тела методов компилируются только на втором этапе.

Анонимные типы не определяются, пока тело метода не скомпилировано.

Таким образом, компилятор не имеет возможности определить тип возвращаемого значения метода на первом этапе.

По этой причине анонимные типы не могут использоваться в качестве возвращаемого типа.

Как предлагали другие, если вы используете .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 и т. Д.


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

Теперь особенно с локальными функциями, но вы всегда можете сделать это, передав делегат, который создает анонимный тип.

Итак, если вашей целью было запустить разную логику для одних и тех же источников и иметь возможность объединить результаты в один список. Не уверен, какой нюанс отсутствует для достижения заявленной цели, но пока вы возвращаете a Tи передаете делегат для make 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);
}

0

Фактически возможно вернуть анонимный тип из метода в конкретном случае использования. Давайте посмотрим!

В 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 #.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.