匿名类可以实现接口吗?

有可能让匿名类型实现接口吗?

我有一段代码,我想工作,但不知道如何做到这一点。

我已经得到了几个答案,要么说不,要么创建一个实现接口的类,构造它的新实例。这并不是很理想,但我想知道是否有一种机制可以在接口之上创建一个精简的动态类,从而使这变得简单。

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

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

183896 次浏览

没有;匿名类型除了具有一些属性外,不能做任何事情。您需要创建自己的类型。我没有深入阅读链接的文章,但它似乎使用了反射。Emit动态创建新类型;但是如果你把讨论限制在在c#内部上,你就不能做你想做的事情。

不,匿名类型不能实现接口。从c#编程指南:

匿名类型是由一个或多个公共只读属性组成的类类型。不允许使用其他类型的类成员,如方法或事件。匿名类型不能强制转换为除对象以外的任何接口或类型。

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

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

注意,您需要将查询的结果强制转换为接口的类型。也许有更好的办法,但我找不到。

将匿名类型转换为接口是我一直想做的事情,但不幸的是,当前的实现迫使您拥有该接口的实现。

围绕它的最佳解决方案是使用某种类型的动态代理为您创建实现。使用优秀的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>();

虽然线程中的答案都足够真实,但我忍不住要告诉你,它实际上是是可能的来让一个匿名类实现一个接口,尽管它需要一些创造性的作弊才能实现。

回到2008年,我正在为我当时的雇主编写一个自定义LINQ提供者,在某个时刻,我需要能够告诉“我的”;来自其他匿名类的匿名类,这意味着让它们实现一个我可以用来进行类型检查的接口。我们解决这个问题的方法是使用方面(我们使用PostSharp),直接在IL中添加接口实现。因此,实际上,让匿名类实现接口是可行的,你只需要稍微改变规则就可以做到这一点。

这个问题的答案是否定的。但是您是否一直在研究模仿框架?我使用的是MOQ,但有数百万个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);
}

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

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

另一种选择是创建一个单独的、具体的实现类,在构造函数中接受lambdas。

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,这就少了很多boiler plate。

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