匿名类能实现接口吗?

512

是否可能让匿名类型实现一个接口?

我有一段代码希望能够运行,但是不知道该如何操作。

我得到了几个答案,要么是否定的,要么就是创建一个实现接口的类并构建该类的新实例。这并不是非常理想,但我想知道是否有一种机制可以在接口之上创建一个轻量级的动态类,从而使这个过程变得简单。

public interface DummyInterface
{
    string A { get; }
    string B { get; }
}

public class DummySource
{
    public string A { get; set; }
    public string C { get; set; }
    public string D { get; set; }
}

public class Test
{
    public void WillThisWork()
    {
        var source = new DummySource[0];
        var values = from value in source
                     select new
                     {
                         A = value.A,
                         B = value.C + "_" + value.D
                     };

        DoSomethingWithDummyInterface(values);

    }

    public void DoSomethingWithDummyInterface(IEnumerable<DummyInterface> values)
    {
        foreach (var value in values)
        {
            Console.WriteLine("A = '{0}', B = '{1}'", value.A, value.B);
        }
    }
}

我找到了一篇文章动态接口封装,它描述了一种方法。这是最好的方法吗?


1
链接似乎已经过期,这可能是一个合适的替代方案:http://www.liensberger.it/web/blog/?p=298。 - Phil Cooper
2
是的,您可以使用.NET 4及更高版本(通过DLR)来完成此操作,使用ImpromptuInterface nuget包。 - BrainSlugs83
1
@PhilCooper,你的链接已经失效了,可能至少从2016年开始就这样了 - 但幸运的是,在那之前它被存档了。https://web.archive.org/web/20111105150920/http://www.liensberger.it/web/blog/?p=298 - Paul
在C#9中,您可以使用记录(https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/)。我知道这不是完全相同的方法,但也是一种非常轻量级的实现“简单”接口的方法 :) - Marco
10个回答

392
不,匿名类型无法实现接口。来自C#编程指南
匿名类型是由一个或多个公共只读属性组成的类类型。不允许使用其他类型的类成员,例如方法或事件。匿名类型无法转换为任何接口或类型,除了对象类型。

8
无论如何,拥有这些东西都是不错的。如果你在谈论代码可读性,Lambda表达式通常不是最佳选择。如果我们谈论RAD(快速应用程序开发),我非常喜欢类似Java的匿名接口实现。顺便说一下,在某些情况下,该功能比委托更强大。 - Arsen Zahray
21
使用得当,Lambda表达式实际上可以增加代码的可读性。当它们用于函数链时特别强大,这可以减少或消除对本地变量的需求。 - Roy Tinker
4
你可以这样完成这个技巧:“匿名实现类——C#的设计模式”- http://twistedoakstudios.com/blog/Post774_anonymous-implementation-classes-a-design-pattern-for-c - Dmitry Pavlov
6
@DmitryPavlov,这真是意外地有价值。路人们:在这里是简化版。 - kdbanman
1
你可以将匿名类型转换为具有相同字段的匿名对象。https://dev59.com/rHM_5IYBdhLWcg3wXx1N - Zinov
显示剩余2条评论

98

虽然这个帖子中的答案都是正确的,但我不禁想告诉你,实际上有可能使用匿名类来实现一个接口,尽管需要一些巧妙的作弊方式才能做到。

早在2008年,我为我的雇主编写了一个自定义LINQ提供程序,其中我需要能够区分“我的”匿名类和其他匿名类,这意味着我需要让它们实现一个接口,以便我可以用来进行类型检查。我们解决方法是使用切面(我们使用PostSharp),直接在IL中添加接口实现。因此,允许匿名类实现接口是可行的,只需要稍微打破规则就可以实现。


9
@Gusdor,在这种情况下,我们完全控制了构建过程,并且它始终在专用机器上运行。此外,由于我们使用的是PostSharp,而我们所做的事情在该框架内是完全合法的,只要我们确保在使用的构建服务器上安装了PostSharp,就不会出现任何问题。 - Mia Clarke
16
@Gusdor 我同意让其他程序员轻松地获取项目并编译,而不需要花费大量的困难,但这是一个可以单独解决的不同问题,而不是完全避免使用像 postsharp 这样的工具或框架。你所提出的论点也可以用来反驳 Visual Studio 本身或任何其他非标准 MS 框架,它们都不是 C# 规范的一部分。你需要这些东西,否则它将会“崩溃”。在我看来,这不是问题。问题在于当构建变得如此复杂时,很难使所有这些东西正确地协同工作。 - AaronLS
6
不,它没有。据我所知,它仍在生产中。当时提出的担忧对我来说很奇怪,现在最多也只是任意性的。基本上是在说“不要使用框架,否则会出问题!”。但实际上框架并没有造成任何问题,什么都没有崩溃,这一点我并不感到惊讶。 - Mia Clarke
5
现在更容易了,不需要在代码生成后修改中间语言(IL); 只需使用ImpromptuInterface。--它可以让你将任何对象(包括匿名类型对象)绑定到任何接口上(当然如果你试图使用类实际上不支持的接口部分,会产生迟绑定异常)。 - BrainSlugs83

49
将匿名类型转换为接口是我想要已久的功能,但不幸的是当前实现强制你必须拥有该接口的实现。
最好的解决方案是使用某种动态代理来为您创建实现。使用出色的LinFu项目,您可以替换。
select new
{
  A = value.A,
  B = value.C + "_" + value.D
};

使用

 select new DynamicObject(new
 {
   A = value.A,
   B = value.C + "_" + value.D
 }).CreateDuck<DummyInterface>();

19
即兴界面项目将使用DLR在.NET 4.0中完成此操作,比Linfu更轻巧。 - jbtule
“DynamicObject”是LinFu类型吗?在.NET 4.5中,“System.Dynamic.DynamicObject”只有一个受保护的构造函数。 - jdmcnair
是的,我指的是早于DLR版本的LinFu实现的DynamicObject。 - Arne Claassen

17

匿名类型可以通过动态代理实现接口。

我在GitHub上编写了一个扩展方法,并在博客文章http://wblo.gs/feE中介绍了支持此场景的方法。

该方法可以像这样使用:

class Program
{
    static void Main(string[] args)
    {
        var developer = new { Name = "Jason Bowers" };

        PrintDeveloperName(developer.DuckCast<IDeveloper>());

        Console.ReadKey();
    }

    private static void PrintDeveloperName(IDeveloper developer)
    {
        Console.WriteLine(developer.Name);
    }
}

public interface IDeveloper
{
    string Name { get; }
}

13

不行;匿名类型除了拥有一些属性之外,不能做其他事情。您需要创建自己的类型。我没有深入阅读链接的文章,但它似乎使用 Reflection.Emit 在运行时创建新类型;但如果您仅限于讨论 C# 内部的东西,那么您无法做到想要的。


需要注意的是:属性可能包括函数或voids(Action):select new { ... MyFunction = new Func<string,bool>(s => value.A == s) } 虽然可以工作,但您不能在函数中引用新属性(我们不能使用“A”代替“value.A”)。 - cfeduke
2
这不就是一个恰好是委托的属性吗?它并不是一个实际的方法。 - Marc Gravell
1
我曾经使用 Reflection.Emit 在运行时创建类型,但现在我认为我更喜欢使用 AOP(面向切面编程)解决方案来避免运行时成本。 - Norman H

10

最好的解决方案是不使用匿名类。

public class Test
{
    class DummyInterfaceImplementor : IDummyInterface
    {
        public string A { get; set; }
        public string B { get; set; }
    }

    public void WillThisWork()
    {
        var source = new DummySource[0];
        var values = from value in source
                     select new DummyInterfaceImplementor()
                     {
                         A = value.A,
                         B = value.C + "_" + value.D
                     };

        DoSomethingWithDummyInterface(values.Cast<IDummyInterface>());

    }

    public void DoSomethingWithDummyInterface(IEnumerable<IDummyInterface> values)
    {
        foreach (var value in values)
        {
            Console.WriteLine("A = '{0}', B = '{1}'", value.A, value.B);
        }
    }
}

请注意,你需要将查询结果强制转换为接口的类型。可能有更好的方法来实现,但我找不到。


2
您可以使用values.OfType<IDummyInterface>()而不是强制转换。它仅返回实际可转换为该类型的集合中的对象。这完全取决于您想要什么。 - Kristoffer L

9

针对所问的问题,答案是否定的。但是你是否已经查看了模拟框架?我使用的是MOQ,但有数百万个不同的框架可供选择,它们可以让你直接实现/存根(部分或全部)接口。例如:

public void ThisWillWork()
{
    var source = new DummySource[0];
    var mock = new Mock<DummyInterface>();

    mock.SetupProperty(m => m.A, source.Select(s => s.A));
    mock.SetupProperty(m => m.B, source.Select(s => s.C + "_" + s.D));

    DoSomethingWithDummyInterface(mock.Object);
}

2
另一个选择是创建一个单一的、具体的实现类,在构造函数中接受lambda表达式。将原始答案翻译为“最初的回答”
public interface DummyInterface
{
    string A { get; }
    string B { get; }
}

// "Generic" implementing class
public class Dummy : DummyInterface
{
    private readonly Func<string> _getA;
    private readonly Func<string> _getB;

    public Dummy(Func<string> getA, Func<string> getB)
    {
        _getA = getA;
        _getB = getB;
    }

    public string A => _getA();

    public string B => _getB();
}

public class DummySource
{
    public string A { get; set; }
    public string C { get; set; }
    public string D { get; set; }
}

public class Test
{
    public void WillThisWork()
    {
        var source = new DummySource[0];
        var values = from value in source
                     select new Dummy // Syntax changes slightly
                     (
                         getA: () => value.A,
                         getB: () => value.C + "_" + value.D
                     );

        DoSomethingWithDummyInterface(values);

    }

    public void DoSomethingWithDummyInterface(IEnumerable<DummyInterface> values)
    {
        foreach (var value in values)
        {
            Console.WriteLine("A = '{0}', B = '{1}'", value.A, value.B);
        }
    }
}

如果你只需要将 DummySource 转换为 DummyInterface,那么只需创建一个类,在构造函数中传入 DummySource 并实现接口即可。但是,如果你需要将许多类型转换为 DummyInterface,这种方法会更加简单。

0
使用Roslyn,您可以动态创建从接口(或抽象类)继承的类。
我使用以下代码从抽象类创建具体类。
在此示例中,AAnimal是一个抽象类。
var personClass = typeof(AAnimal).CreateSubclass("Person");

然后你可以实例化一些对象:

var person1 = Activator.CreateInstance(personClass);
var person2 = Activator.CreateInstance(personClass);

毫无疑问,这并不适用于每种情况,但应该足以让您开始:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

namespace Publisher
{
    public static class Extensions
    {
        public static Type CreateSubclass(this Type baseType, string newClassName, string newNamespace = "Magic")
        {
            //todo: handle ref, out etc.
            var concreteMethods = baseType
                                    .GetMethods()
                                    .Where(method => method.IsAbstract)
                                    .Select(method =>
                                    {
                                        var parameters = method
                                                            .GetParameters()
                                                            .Select(param => $"{param.ParameterType.FullName} {param.Name}")
                                                            .ToString(", ");

                                        var returnTypeStr = method.ReturnParameter.ParameterType.Name;
                                        if (returnTypeStr.Equals("Void")) returnTypeStr = "void";

                                        var methodString = @$"
                                        public override {returnTypeStr} {method.Name}({parameters})
                                        {{
                                            Console.WriteLine(""{newNamespace}.{newClassName}.{method.Name}() was called"");
                                        }}";

                                        return methodString.Trim();
                                    })
                                    .ToList();

            var concreteMethodsString = concreteMethods
                                        .ToString(Environment.NewLine + Environment.NewLine);

            var classCode = @$"
            using System;

            namespace {newNamespace}
            {{
                public class {newClassName}: {baseType.FullName}
                {{
                    public {newClassName}()
                    {{
                    }}

                    {concreteMethodsString}
                }}
            }}
            ".Trim();

            classCode = FormatUsingRoslyn(classCode);


            /*
            var assemblies = new[]
            {
                MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
                MetadataReference.CreateFromFile(baseType.Assembly.Location),
            };
            */

            var assemblies = AppDomain
                        .CurrentDomain
                        .GetAssemblies()
                        .Where(a => !string.IsNullOrEmpty(a.Location))
                        .Select(a => MetadataReference.CreateFromFile(a.Location))
                        .ToArray();

            var syntaxTree = CSharpSyntaxTree.ParseText(classCode);

            var compilation = CSharpCompilation
                                .Create(newNamespace)
                                .AddSyntaxTrees(syntaxTree)
                                .AddReferences(assemblies)
                                .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

            using (var ms = new MemoryStream())
            {
                var result = compilation.Emit(ms);
                //compilation.Emit($"C:\\Temp\\{newNamespace}.dll");

                if (result.Success)
                {
                    ms.Seek(0, SeekOrigin.Begin);
                    Assembly assembly = Assembly.Load(ms.ToArray());

                    var newTypeFullName = $"{newNamespace}.{newClassName}";

                    var type = assembly.GetType(newTypeFullName);
                    return type;
                }
                else
                {
                    IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
                        diagnostic.IsWarningAsError ||
                        diagnostic.Severity == DiagnosticSeverity.Error);

                    foreach (Diagnostic diagnostic in failures)
                    {
                        Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
                    }

                    return null;
                }
            }
        }

        public static string ToString(this IEnumerable<string> list, string separator)
        {
            string result = string.Join(separator, list);
            return result;
        }

        public static string FormatUsingRoslyn(string csCode)
        {
            var tree = CSharpSyntaxTree.ParseText(csCode);
            var root = tree.GetRoot().NormalizeWhitespace();
            var result = root.ToFullString();
            return result;
        }
    }
}

-1
通常情况下,你只是想避免实现一个公共类,这个类只是为了一个非常特定的目的而存在。例如,当你有一个数据生成器类时,你希望尽可能地保持它的无依赖性,这样你就可以在生成该接口实例的类中创建一个私有记录来实现你的接口,并返回该私有记录的实例。
在你的情况下:
public class Test
{
    public void WillThisWork()
    {
        var source = new DummySource[0];
        var values = from value in source
            select new DummyImplementation(value.A, value.C + "_" + value.D);

       DoSomethingWithDummyInterface(values);

    }
     
    public void DoSomethingWithDummyInterface(IEnumerable<DummyInterface> values)
    {
        foreach (var value in values)
        {
            Console.WriteLine("A = '{0}', B = '{1}'", value.A, value.B);
        }
    }

    private record DummyImplementation(string A, string B) : DummyInterface { }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接