I would like to modify my current LetterFactory implementation and remove the call to Activator.CreateInstance with a call to the container to resolve the current Letter fully initialized with constructor injection. I have read the docs here and here, and even this SO Post while penning this post, but nothing seems to click.
Notes:
1) IDocumentServicesCore is an Aggregate.
2) All Letters are decorated with the LetterTypeAttribute (hundreds of them)
3) This LetterFactory itself is registered in the container.
public class LetterFactory : ILetterFactory
{
private readonly IDocumentServicesCore _documentServicesCore;
public LetterFactory(IDocumentServicesCore documentServicesCore)
{
_documentServicesCore = documentServicesCore;
}
public LetterBase Create(int letterId)
{
if (letterId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(letterId));
}
List<Type> types = typeof(LetterBase).Assembly.GetTypes()
.Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(LetterBase)))
.ToList();
LetterBase letter = null;
foreach(Type type in types)
{
LetterTypeAttribute attribute = type.GetCustomAttributes<LetterTypeAttribute>().First();
if (!attribute.LetterId.Contains(letterId))
{
continue;
}
letter = Activator.CreateInstance(type, _documentServicesCore) as LetterBase;
break;
}
if (letter != null)
{
return letter;
}
string message = $"Could not find a LetterBase to create for id {letterId}.";
throw new NotSupportedException(message);
}
}
Update1
The problems seems to start with the fact that the letters themselves aren't registered, how to I take the LINQ code that collects the Letters from the assembly and register those enmass?
Thank you,
Stephen
You are looking for IIndex<TKey, TValue> which is a kind of dictionary and it can be composed so IIndex<Int32, Func<LetterBase>> is the type you want.
With such a type your LetterFactory will look like this :
public class LetterFactory : ILetterFactory
{
private readonly IIndex<Int32, Func<LetterBase>> _lettersFactory;
public LetterFactory(IIndex<Int32, Func<LetterBase>> lettersFactory)
{
_lettersFactory = lettersFactory;
}
public LetterBase Create(int letterId)
{
if (letterId <= 0)
{
throw new ArgumentOutOfRangeException(nameof(letterId));
}
Func<LetterBase> letterFactory = null;
if(!this._lettersFactory.tryGetValue(letterId, out letterFactory))
{
string message = $"Could not find a LetterBase to create for id {letterId}.";
throw new NotSupportedException(message);
}
Letter letter = letterFactory();
return letter;
}
}
And then you have to register your types like this :
List<Type> letterTypes = typeof(LetterBase).Assembly.GetTypes()
.Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(LetterBase)))
.ToList();
foreach(Type letterType in letterTypes)
{
LetterTypeAttribute attribute = type.GetCustomAttributes<LetterTypeAttribute>()
.First();
builder.RegisterType(letterType)
.Keyed<LetterBase>(attribute.LetterId);
}
You will also improve performance with this code : the heavy assembly scanning will only happen once at startup and not for each call.
By the way, be aware of assembly scanning limitation in IIS hosted application : http://autofaccn.readthedocs.io/en/latest/register/scanning.html#iis-hosted-web-applications
You can also rely directly on IIndex<Int32, LetterBase> instead of IIndex<Int32, Func<LetterBase>> it depends on your scope strategy.
You made me do real work, good job :) The following is my solution.
Autofac - Named and Keyed Services - Resolving with Index
using System;
using System.Collections.Generic;
using System.Linq;
using Autofac;
using Autofac.Features.Indexed;
public class Program
{
private static IContainer _Container;
public static void Main()
{
InitDependencyInjection();
var rd1 = _Container.Resolve<RequiresDependency>(new NamedParameter("letterId", 1));
rd1.PrintType();
var rd2 = _Container.Resolve<RequiresDependency>(new NamedParameter("letterId", 2));
rd2.PrintType();
}
private static void InitDependencyInjection()
{
var builder = new ContainerBuilder();
var letterTypes = typeof(LetterBase).Assembly.GetTypes()
// Find all types that derice from LetterBase
.Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(LetterBase)))
// Make sure they are decorated by attribute
.Where(t =>
t.GetCustomAttributes(typeof(LetterTypeAttribute), false).Length == 1)
.ToList();
//Register with Autofac, Keyed by LetterId
//This should throw an exception if any are duplicated
//You may want to consider using an enum instead
//It's not hard to convert an Int to Enum
foreach(Type letterType in letterTypes)
{
// we already tested the type has the attribute above
var attribute = letterType
.GetCustomAttributes(typeof(LetterTypeAttribute)
, false)[0] as LetterTypeAttribute;
builder.RegisterType(letterType)
.Keyed<LetterBase>(attribute.LetterId);
}
builder.RegisterType<RequiresDependency>();
_Container = builder.Build();
}
}
public class RequiresDependency
{
private readonly LetterBase _letter;
//Autofac automagically provides a factory that returns type
//type you need via indexer
public RequiresDependency(int letterId, IIndex<int, LetterBase> letterFactory)
{
//resolve the needed type based on the index value passed in
_letter = letterFactory[letterId];
}
public void PrintType()
{
Console.WriteLine(_letter.GetType().Name);
}
}
public abstract class LetterBase
{
}
[LetterType(1)]
public class LetterA : LetterBase
{}
[LetterType(2)]
public class LetterB : LetterBase
{}
// make sure the classes using this attribute has only a single attribute
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class LetterTypeAttribute : Attribute
{
public LetterTypeAttribute(int letterId)
{
LetterId = letterId;
}
public int LetterId { get; private set; }
}
DotNetFiddle Example
Result:
LetterA
LetterB
Related
This is a command system that runs using Command Attributes. An example of how this works is listed below.
If you were to type /message in the chat, this will run the method in the entry assembly that contains a CommandAttribute with the Text value of "message". All Classes that use the CommandAttribute inherit from the CommandContext Class. Using reflection, I am trying to set the value of the CommandContext properties so that they can be used in the Derived Class which would contain the Command Method that was invoked.
When setting the value of a property located in the CommandContext Class (Message in this case) I am receiving the following error.
Object does not match target type
I have tried the solutions from other questions but I am still receiving the error.
I have posted the Derived class, Base Class, and the Method below. Please let me know if there is any other information needed to help me out. Thank you all for your help.
Error is occurring here:
messageProp.SetValue(baseType, Convert.ChangeType(rawMessage, messageProp.PropertyType), null);
COMMAND ATTRIBUTE
namespace RocketNET.Attributes
{
public class CommandAttribute : Attribute
{
public string Text { get; private set; }
public CommandAttribute(string text)
{
Text = text;
}
}
}
BASE CLASS
namespace RocketNET
{
public class CommandContext
{
public string Message { get; internal set; }
public CommandContext() { }
}
}
DERIVED CLASS
namespace ACGRocketBot.Commands
{
public class Maintenance : CommandContext
{
[Command("message")]
public void SendMessage()
{
Console.WriteLine(Message);
}
}
}
METHOD
namespace RocketNET
{
public class RocketClient
{
private void MessageReceived(object sender, MessageEventArgs e)
{
string rawMessage = "/message";
if (rawMessage[0] == _commandPrefix)
{
var method = Assembly.GetEntryAssembly()
.GetTypes()
.SelectMany(t => t.GetMethods())
.FirstOrDefault(m =>
m.GetCustomAttribute<CommandAttribute>()?.Text == rawMessage.Substring(1).ToLower());
if (method != null)
{
method.Invoke(Activator.CreateInstance(method.DeclaringType), null);
var baseType = method.DeclaringType.BaseType;
var messageProp = baseType.GetProperty("Message");
messageProp.SetValue(baseType, Convert.ChangeType(rawMessage, messageProp.PropertyType), null);
}
}
}
}
}
The first argument of the PropertyInfo.SetValue method is the instance whose property you want to set (or null for static properties). You pass in an instance of Type instead of an instance of CommandContext. Hence you get the error.
However, you don't even need to use reflection to set the CommandContext.Message property. You know that method.DeclaringType is of type CommandContext so you can simply downcast the object returned by Activator.CreateInstance:
// ...
if (method != null)
{
var commandContext = (CommandContext)Activator.CreateInstance(method.DeclaringType);
commandContext.Message = rawMessage;
method.Invoke(commandContext, null);
}
// ...
(I reversed the order of the method invocation and the setting of Message property so that your code makes sense, otherwise Maintenance.SendMessage wouldn't print anything.)
Bonus code review
The following part should be optimized:
var method = Assembly.GetEntryAssembly()
.GetTypes()
.SelectMany(t => t.GetMethods())
.FirstOrDefault(m =>
m.GetCustomAttribute<CommandAttribute>()?.Text == rawMessage.Substring(1).ToLower());
Reflection is slow. Scanning your assembly for marked methods each time your event handler called will degrade the performance of your application. Type metadata won't change during the run of your application so you can easily implement some kind of caching here:
private delegate void CommandInvoker(Action<CommandContext> configure);
private static CommandInvoker CreateCommandInvoker(MethodInfo method)
{
return cfg =>
{
var commandContext = (CommandContext)Activator.CreateInstance(method.DeclaringType);
cfg(commandContext);
method.Invoke(commandContext, null);
};
}
private static readonly IReadOnlyDictionary<string, CommandInvoker> commandCache = Assembly.GetEntryAssembly()
.GetTypes()
.Where(t => t.IsSubclassOf(typeof(CommandContext)) && !t.IsAbstract && t.GetConstructor(Type.EmptyTypes) != null)
.SelectMany(t => t.GetMethods(), (t, m) => new { Method = m, Attribute = m.GetCustomAttribute<CommandAttribute>() })
.Where(it => it.Attribute != null)
.ToDictionary(it => it.Attribute.Text, it => CreateCommandInvoker(it.Method));
// now MessageReceived becomes as simple as:
private void MessageReceived(object sender, MessageEventArgs e)
{
string rawMessage = "/message";
if (rawMessage.StartsWith('/') && commandCache.TryGetValue(rawMessage.Substring(1), out CommandInvoker invokeCommand))
invokeCommand(ctx => ctx.Message = rawMessage);
}
You can go even further and completely eliminate the need of reflection during execution by using expression trees instead of method.Invoke:
private static CommandInvoker CreateCommandInvoker(MethodInfo method)
{
var configureParam = Expression.Parameter(typeof(Action<CommandContext>));
var commandContextVar = Expression.Variable(method.DeclaringType);
var bodyBlock = Expression.Block(new[] { commandContextVar }, new Expression[]
{
Expression.Assign(commandContextVar, Expression.New(method.DeclaringType)),
Expression.Invoke(configureParam, commandContextVar),
Expression.Call(commandContextVar, method),
});
var lambda = Expression.Lambda<CommandInvoker>(bodyBlock, configureParam);
return lambda.Compile();
}
Very new to C# so forgive me if this is a silly question.
If I have a base class called Validator, and a number of classes which inherit from this class such as validateFirstname, validateSecondname etc... is it possible to write a method which will loop through each of these subclasses and instantiate each?
Something along the lines of
public class loadValidators
{
public loadValidators()
{
foreach (subclass in class)
{
// instantiate class here
}
}
}
Any help is much appreciated as always.
Try this:
var validator_type = typeof (Validator);
var sub_validator_types =
validator_type
.Assembly
.DefinedTypes
.Where(x => validator_type.IsAssignableFrom(x) && x != validator_type)
.ToList();
foreach (var sub_validator_type in sub_validator_types)
{
Validator sub_validator = (Validator)Activator.CreateInstance(sub_validator_type);
}
This code assumes that all the sub classes live in the same assembly/project as the Validator class.
Also, it assumes that each of the subclasses have a public parameterless constructor.
Please note that I would not recommend this approach.
Instead you should do something like this to solve your problem (of modeling/using multiple validators):
public interface IValidator
{
bool Validate(SomeObject something);
}
public class FirstNameValidator : IValidator
{
public bool Validate(SomeObject something)
{
...
}
}
public class LastNameValidator : IValidator
{
public bool Validate(SomeObject something)
{
...
}
}
public class CompositeValidator : IValidator
{
private readonly IValidator[] m_Validators;
public CompositeValidator(params IValidator[] validators)
{
m_Validators = validators;
}
public bool Validate(SomeObject something)
{
foreach (IValidator validator in m_Validators)
{
if (!validator.Validate(something))
return false;
}
return true;
}
}
The CompositeValidator wraps multiple validators and knows how to validate objects using those validators.
You can use it like this:
var composite_validator = new CompositeValidator(new FirstNameValidator() , new LastNameValidator());
composite_validator.Validate(obj);
I have a ASP MVC 4 app that uses Structuremap. I'm trying to add logging to my application via Structuremap interception.
In a Registry, I scan a specific assembly in order to register all of it's types with the default convention:
public class ServicesRegistry : Registry
{
public ServicesRegistry()
{
Scan(x =>
{
x.AssemblyContainingType<MyMarkerService>();
x.WithDefaultConventions();
});
}
}
The interceptor:
public class LogInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
var watch = Stopwatch.StartNew();
invocation.Proceed();
watch.Stop();//log the time
}
}
I can add the interceptor for one specific plugin type like this:
var proxyGenerator = new ProxyGenerator();
container.Configure(x => x.For<IServiceA>().Use<ServiceA>().DecorateWith(instance => proxyGenerator.CreateInterfaceProxyWithTarget(instance, new LogInterceptor())));
but I want to make structuremap create logging proxies for all the types that were scanned in the registry.
Is there a way to achieve this?
It doesn't look like there's an easy extension point for this, but I got it working with a fairly decent solution using a custom convention. In order to help you understand the decisions I made I'll walk you through a few steps (skipping the many, many missteps I made on my way).
First lets look at the DefaultConvention which you are already using.
DefaultConvention:
public class DefaultConventionScanner : ConfigurableRegistrationConvention
{
public override void Process(Type type, Registry registry)
{
if (!TypeExtensions.IsConcrete(type))
return;
Type pluginType = this.FindPluginType(type);
if (pluginType == null || !TypeExtensions.HasConstructors(type))
return;
registry.AddType(pluginType, type);
this.ConfigureFamily(registry.For(pluginType, (ILifecycle)null));
}
public virtual Type FindPluginType(Type concreteType)
{
string interfaceName = "I" + concreteType.Name;
return Enumerable.FirstOrDefault<Type>((IEnumerable<Type>)concreteType.GetInterfaces(), (Func<Type, bool>)(t => t.Name == interfaceName));
}
}
Pretty simple, we get the type and interface pairs and check to make sure they have a constructor, if they do we register them. It would be nice to just modify this so that it calls DecorateWith, but you can only call that on For<>().Use<>(), not For().Use().
Next lets look at what DecorateWith does:
public T DecorateWith(Expression<Func<TPluginType, TPluginType>> handler)
{
this.AddInterceptor((IInterceptor) new FuncInterceptor<TPluginType>(handler, (string) null));
return this.thisInstance;
}
So this creates a FuncInterceptor and registers it. I spent a fair bit of time trying to create one of these dynamically with reflection before deciding it would just be easier to make a new class:
public class ProxyFuncInterceptor<T> : FuncInterceptor<T> where T : class
{
public ProxyFuncInterceptor() : base(x => MakeProxy(x), "")
{
}
protected ProxyFuncInterceptor(Expression<Func<T, T>> expression, string description = null)
: base(expression, description)
{
}
protected ProxyFuncInterceptor(Expression<Func<IContext, T, T>> expression, string description = null)
: base(expression, description)
{
}
private static T MakeProxy(T instance)
{
var proxyGenerator = new ProxyGenerator();
return proxyGenerator.CreateInterfaceProxyWithTarget(instance, new LogInterceptor());
}
}
This class just makes it easier to work with when we have the type as a variable.
Finally I've made my own Convention based on the Default convention.
public class DefaultConventionWithProxyScanner : ConfigurableRegistrationConvention
{
public override void Process(Type type, Registry registry)
{
if (!type.IsConcrete())
return;
var pluginType = this.FindPluginType(type);
if (pluginType == null || !type.HasConstructors())
return;
registry.AddType(pluginType, type);
var policy = CreatePolicy(pluginType);
registry.Policies.Interceptors(policy);
ConfigureFamily(registry.For(pluginType));
}
public virtual Type FindPluginType(Type concreteType)
{
var interfaceName = "I" + concreteType.Name;
return concreteType.GetInterfaces().FirstOrDefault(t => t.Name == interfaceName);
}
public IInterceptorPolicy CreatePolicy(Type pluginType)
{
var genericPolicyType = typeof(InterceptorPolicy<>);
var policyType = genericPolicyType.MakeGenericType(pluginType);
return (IInterceptorPolicy)Activator.CreateInstance(policyType, new object[]{CreateInterceptor(pluginType), null});
}
public IInterceptor CreateInterceptor(Type pluginType)
{
var genericInterceptorType = typeof(ProxyFuncInterceptor<>);
var specificInterceptor = genericInterceptorType.MakeGenericType(pluginType);
return (IInterceptor)Activator.CreateInstance(specificInterceptor);
}
}
Its almost exactly the same with one addition, I create an interceptor and interceptorType for each type we register. I then register that policy.
Finally, a few unit tests to prove it works:
[TestFixture]
public class Try4
{
[Test]
public void Can_create_interceptor()
{
var type = typeof (IServiceA);
Assert.NotNull(new DefaultConventionWithProxyScanner().CreateInterceptor(type));
}
[Test]
public void Can_create_policy()
{
var type = typeof (IServiceA);
Assert.NotNull(new DefaultConventionWithProxyScanner().CreatePolicy(type));
}
[Test]
public void Can_register_normally()
{
var container = new Container();
container.Configure(x => x.Scan(y =>
{
y.TheCallingAssembly();
y.WithDefaultConventions();
}));
var serviceA = container.GetInstance<IServiceA>();
Assert.IsFalse(ProxyUtil.IsProxy(serviceA));
Console.WriteLine(serviceA.GetType());
}
[Test]
public void Can_register_proxy_for_all()
{
var container = new Container();
container.Configure(x => x.Scan(y =>
{
y.TheCallingAssembly();
y.Convention<DefaultConventionWithProxyScanner>();
}));
var serviceA = container.GetInstance<IServiceA>();
Assert.IsTrue(ProxyUtil.IsProxy(serviceA));
Console.WriteLine(serviceA.GetType());
}
[Test]
public void Make_sure_I_wait()
{
var container = new Container();
container.Configure(x => x.Scan(y =>
{
y.TheCallingAssembly();
y.Convention<DefaultConventionWithProxyScanner>();
}));
var serviceA = container.GetInstance<IServiceA>();
serviceA.Wait();
}
}
}
public interface IServiceA
{
void Wait();
}
public class ServiceA : IServiceA
{
public void Wait()
{
Thread.Sleep(1000);
}
}
public interface IServiceB
{
}
public class ServiceB : IServiceB
{
}
There's definitely room for some clean up here (caching, make it DRY, more tests, make it easier to configure) but it works for what you need and is a pretty reasonable way of doing it.
Please ask if you have any other questions about it.
Consider following strucure as a registration subject with Autofac 3.0.0:
class Something
{
public int Result { get; set; }
}
class SomethingGood : Something
{
private int _good;
public int GoodResult {
get { return _good + Result; }
set { _good = value; }
}
}
interface IDo<in T> where T : Something
{
int Calculate( T input );
}
class MakeSomethingGood : IDo<SomethingGood>
{
public int Calculate( SomethingGood input ) {
return input.GoodResult;
}
}
class ControlSomething
{
private readonly IDo<Something> _doer;
public ControlSomething( IDo<Something> doer ) {
_doer = doer;
}
public void Show() {
Console.WriteLine( _doer.Calculate( new Something { Result = 5 } ) );
}
}
I'm trying to register concrete type MakeSomethingGood and then resolve it by contravariant interface.
var builder = new ContainerBuilder();
builder.Register( c => new MakeSomethingGood() ).As<IDo<SomethingGood>>();
builder.Register( c => new ControlSomething( c.Resolve<IDo<Something>>() ) ).AsSelf();
var container = builder.Build();
var controller = container.Resolve<ControlSomething>();
... and Resolve fails because no components found for IDo<Something>
What am I doing wrong?
Thank you
You register an IDo<SomethingGood> and try to resolve an IDo<Something>. How is that ever supposed to work? For this to work, IDo<T> should be defined as covariant: IDo<out T>.
Since IDo<in T> is defined as contravariant (using the in keyword), you can't simply assign an IDo<SomethingGood> to IDo<Something>. This won't compile in C#:
IDo<SomethingGood> good = new MakeSomethingGood();
// Won't compile
IDo<Something> some = good;
And that's why Autofac can't resolve it, even with the ContravariantRegistrationSource.
I'm currently using MEF in my project, however, a legacy component uses Castle to export all its components.
I would like to be able to Import from this kernel when creating new objects, in addition to getting the exports from the Xap.
Is this possible? Can you show me some example code?
MEF was designed to be as flexible as possible, and one of its secretly hidden but real nice features, is the ability to define new ExportProvider instances, that allow you to plug in additional components. I've talked about this previously by utilising the Common Service Locator project in an ASP.NET MVC with MEF Project (see part 3 here).
The CSL is a nice flexible approach, as there are many specific CSL implementations for many of the existing IoC containers, such as Castle, Autofac, Ninject, Unity etc.
Another good example can be found here, which demonstrates a slightly different, but fundamentally similar approach.
As Matthew correctly said, the way to do this is using an ExportProvider
Another example is here (it demonstrates exports from Xaml).
Below is what I did in the end to solve the problem.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.Linq;
using Castle.MicroKernel;
using Castle.MicroKernel.Registration;
using Castle.Windsor;
namespace MEFCastleBridge
{
public class CastleExportProvider : ExportProvider
{
WindsorContainer _container;
private readonly Dictionary<ExportDefinition, List<Export>> _exports =
new Dictionary<ExportDefinition, List<Export>>();
private readonly object _sync = new object();
public CastleExportProvider(WindsorContainer container)
{
_container = container;
var handlers = _container.Kernel.GetAssignableHandlers(typeof(object));
foreach (var handler in handlers)
{
RegisterCastleComponent(handler);
}
_container.Kernel.ComponentRegistered += ComponentRegistered;
}
protected override IEnumerable<Export> GetExportsCore(
ImportDefinition definition, AtomicComposition atomicComposition)
{
var contractDefinition = definition as ContractBasedImportDefinition;
var retVal = Enumerable.Empty<Export>();
if (contractDefinition != null)
{
string contractName = contractDefinition.ContractName;
if (!string.IsNullOrEmpty(contractName))
{
var exports =
from e in _exports
where string.Compare(e.Key.ContractName, contractName, StringComparison.OrdinalIgnoreCase) == 0
select e.Value;
if (exports.Count() > 0)
{
retVal = exports.First();
}
}
}
return retVal;
}
void RegisterCastleComponent(IHandler handler)
{
var type = handler.Service;
var contractName = type.ToString();
lock (_sync)
{
var found = from e in _exports
where string.Compare(e.Key.ContractName,
contractName, StringComparison.OrdinalIgnoreCase) == 0
select e;
if (found.Count() == 0)
{
var metadata = new Dictionary<string, object>();
var definition = new ExportDefinition(contractName, metadata);
_exports.Add(definition, new List<Export>());
}
var wrapper = new Export(contractName, () => _container.Resolve(type));
found.First().Value.Add(wrapper);
}
}
void ComponentRegistered(string key, IHandler handler)
{
RegisterCastleComponent(handler);
}
}
public interface IMyComponent
{
string TheString { get; }
}
public class RegisteredComponent : IMyComponent
{
public string TheString { get { return "RegisteredComponent"; } }
}
[Export(typeof(IMyComponent))]
public class ExportedComponent : IMyComponent
{
public string TheString { get { return "ExportedComponent"; } }
}
public class ExportExample
{
// Will contain an instance of RegisteredComponent and ExportedComponent
[ImportMany]
public List<IMyComponent> Components { get; set; }
public ExportExample()
{
// Create a Windsor container and add a type.
var container = new WindsorContainer();
container.Register(Component.For<IMyComponent>().ImplementedBy<MyComponent>().LifeStyle.Singleton);
// Add the Export Provider, in addition to the DeploymentCatalog
var compContainer = new CompositionContainer(new DeploymentCatalog(), new CastleExportProvider(container));
// Should only be called once, before any attempt to SatisfyImports.
CompositionHost.Initialize(compContainer);
CompositionInitializer.SatisfyImports(this);
Test = string.Join(", ", Components.Select(c => c.DoSomething));
}
public string Test { get; set; }
}
}