Casting and Generics constraints - c#

I have this event class:
sealed class AnEvent : EventArgs
{
IEnumerable<ItemWrapper<AnAbstractClass>> Items { get; set; }
}
Which is used like this:
class ItemsProcessor
{
delegate void OnItemsProcessedHandler(object sender, AnEvent e);
event OnItemsProcessedHandler OnItemsProcessed;
//...
}
I use this wrapper class:
sealed class ItemWrapper<T>
where T: AnAbstractClass
{
T Item { get; set; }
Metadata Metadata { get; set; }
ItemWrapper(T item, Metadata metadata)
{
Item = item;
Metadata = metadata;
}
}
And I have this method in ItemsProcessor class :
internal void DoSomethingWithList<T>(IEnumerable<T> items)
where T: AnAbstractClass, new()
{
IEnumerable<ItemWrapper<T>> processedItems = WrapItems<T>(items);
OnItemsProcessed(this, new AnEvent() { Items = processedItems }); //error here
}
The issue is on the last line of this code sample; when I try to set the property Items of AnEvent with my local IEnumerable. The compiler refuses to proceed telling me that it cannot implicitly cast an IEnumerable<ItemWrapper<T>> to an IEnumerable<ItemWrapper<AnAbstractClass>>. I thought it should be okay since I added the constraint where T: AnAbstractClass, new() for this method but even when explicitly casting (using either classic casting with parenthesis or using Convert<>) I get an InvalidCastException.
My current workaround for the method DoSomethingWithList is :
var temp = processedItems.Select(x =>
{
return new ItemWrapper<AnAbstractClass>(x.Item, x.Metadata);
});
OnItemsProcessed(this, new AnEvent() { Items = temp });
So it is working fine now but I was wondering why couldn't it work without using this LINQ conversion which has to iterate over all the items in the list? It seems obvious to me that you should be able to cast it without any error since I added the constraint and even with an explicit cast to make the compiler accepts my code there is an exception raised... Anyone could point me what is going wrong here?
abstract class AnAbstractClass
{
}
class ItemClass : AnAbstractClass
{
}
A WrapItems quick implementation for easy copy-pasting if you want to try:
IEnumerable<ItemWrapper<T>> WrapItems<T>(IEnumerable<T> items)
where T : AnAbstractClass, new()
{
List<ItemWrapper<T>> ret = new List<ItemWrapper<T>>();
foreach (var item in items)
{
ret.Add(new ItemWrapper<T>(item, new Metadata()));
}
return ret;
}

If you change ItemWrapper to
sealed class ItemWrapper
{
AnAbstractClass Item { get; set; }
Metadata Metadata { get; set; }
ItemWrapper(AnAbstractClass item, Metadata metadata)
{
Item = item;
Metadata = metadata;
}
}
what have you lost?

Related

Argument 'SpecificGroupType1' is not assignable to parameter type 'Group<IItem>'

I'm trying to put together an architecture like this:
Section
Group
Item
Attribute
Attribute
Group
Item
Attribute
Attribute
Section
[...]
I'm then trying instantiate this architecture like this:
var sections = new List<ISection>
{
new Section("Section Header", new List<Group<IItem>>
{
new SpecificGroupType1(token, "GroupName")
}
};
The SpecificGroupType1 then spins up a new list of the appropriate IItem type.
I'm getting the following error, though:
Argument SpecificGroupType1 is not assignable to parameter type Group<IItem>
I'm not quite sure why, though, because SpecificGroupType1 inherits from Group.
The full architecture looks like this (I omitted the IAttribute stuff, because the issue I'm running into happens before IAttribute stuff even gets involved):
Section.cs
public interface ISection { // Stuff }
public class Section : ISection
{
public Section(string sectionName, IList<Group<IItem>> groups)
{
Name = sectionName;
Groups = groups;
}
}
Group.cs
public interface IGroup { // Stuff }
public abstract class Group<T> : IGroup where T : IItem
{
protected Group(JToken token, string groupName)
{
Name = groupName;
Items = new List<IItem>();
foreach (var itemToken in Token.Children())
{
Items.Add((Item)Activator.CreateInstance(typeof(T), itemToken);
}
}
public string Name { get; internal set; }
public JToken Token { get; internal set; }
protected IList<IItem> Items { get; set; }
}
SpecificGroupType1.cs
public class SpecificGroupType1 : Group<SpecificItemType1>
{
public SpecificGroupType1(JToken token, string groupName) : base(token, groupName) {}
// Stuff
}
Item.cs
public interface IItem { // Stuff }
public abstract class Item : IItem
{
protected ConfigurationItem(JToken token)
{
Attributes = new List<IAttribute>();
Token = token;
}
public IList<IAttribute> Attributes { get; set; }
public JToken Token { get; set; }
}
SpecificItemType1.cs
public class SpecificItemType1 : Item
{
public SpecificItemType1(JToken token) : base(token) {}
// Stuff
}
Fundamentally, this is a problem with your generic parameters. Consider this simplified example.
// does not compile
Group<IItem> g = new SpecificGroupType1(token, "GroupName");
// does compile
Group<SpecificItemType1> g = new SpecificGroupType1(token, "GroupName");
The problem is that SpecificGroupType1 implements the class Group<SpecificItemType1>, which is not the same as Group<IItem>. If you want to be able to use more derived generic parameter types this way, you need to use a covariant generic parameter declaration. In C#, that's only possible on interfaces, not classes, so you may need to refactor a bit. It would be something like this.
interface IGroup<out T> : IGroup where T: IItem {
// declarations
}
Note the out keyword.

List of various slightly different generic objects

I am merging some code bases into one and am trying to figure out a clever way to merge some slightly different generic objects into a list that builds some of the UI for filtering.
I have many Manager objects that produce and manage ResultSets that are built on top of some of the application base classes.
Any ideas would be great. I am trying not to refactor old deep code as much as possible.
CityManager is something like
ImAFilterSetManager<ImAFilterSetBase<CityBase>>
and ChainManger is something like
ImAFilterSetManager<ImAFilterSetBase<ChainBase>>
The Manager executes the Initialize and returns a ImAFilterSetBase and wires the handler.
Is there a way to cast to something like below?
ImAFilterSetManager<ImAFilterSetBase<object>>
Execution code
List<object> filters = new List<object>() {
new CityManager(),
new ChainManager(), }
//(XXXX as object fails)
foreach (ImAFilterSetManager<ImAFilterSetBase<XXXX>> filter in filters)
{
var newFilter = filter.Initialize(_Client);
filter.OnResultsChanged += filterResults_Handler;
}
It does seem if use dyanmic i can Initialize (or at least it compliles and runs, havent tried much else) but I'm a little worried that would be bad form or cause side effects.
foreach (dynamic filter in filters)
{
var newFilter = filter.Initialize(_Client);
}
Interfaces for reference ( generic I is a ImAFilterSetBase(CityBase) and generic C would be CityBase or ChainBase class )
public interface ImAFilterSetManager<I>
{
event EventHandler<ResultSetArgs> OnResultsChanged;
I Initialize(IClient client);
}
public interface ImAFilterSetBase<C>
{
string FilterName { get; set; }
List<C> Filter { get; set; }
}
In C#, Generic<A> and Generic<B> are not related, unless you make them related. Create another non-generic class (or interface) - FilterSetManager, and have all your ImAFilterSetManager<T> derive from that, or implement that.
Then you can have a List<FilterSetManager>.
You may think in the Liskov Substitution Principle (SOLID), so a good way is relate the objects via an interface:
//defines the contract
public interface IObjectBase {
//add signatures
}
class CityBase : IObjectBase /*, IAnotherBaseA */ {
//implementation
}
class ChainBase : IObjectBase /*, IAnotherBaseB */ {
//implementation
}
Now we are going to create a constraint for the ImAFilterSetBase and rename it to AFilterSetBase
public abstract class AFilterSetBase<T> where T : IObjectBase /*, new() */ {
public string FilterName { get; set; }
public IList<T> Filters { get; set; }
}
I am going to redefine the interface ImAFilterSetManager and rename it to IFilterSetManager
public interface IFilterSetManager {
event EventHandler<ResultSetArgs> OnResultsChanged;
AFilterSetBase<IObjectBase> Initialize(IClient client);
}
Now we can create the classes that implements IFilterSetManager:
public class CityManager : IFilterSetManager {
public AFilterSetBase<IObjectBase> Initialize(IClient client) {
//TODO: implementation
throw new NotImplementedException();
}
}
//other classes that implements IFilterSetManager
class ChainManager : IFilterSetManager {
public AFilterSetBase<IObjectBase> Initialize(IClient client) {
throw new NotImplementedException();
}
}
Finally, in the end-class we can create the list as follow:
static void Main() {
IClient _client;
//_client = new ...
var filters = new List<IFilterSetManager>() {
new CityManager(),
new ChainManager()
};
foreach (var item in filters) {
var newFilter = item.Initialize(_client);
}
}
IMPLEMENTATION
An example implementation for CityManager could be as follow:
class CityFilter : AFilterSetBase<IObjectBase> {
public CityFilter(string filterName) {
this.FilterName = filterName;
this.Filters = new List<IObjectBase>();
}
}
public class CityManager : IFilterSetManager {
public AFilterSetBase<IObjectBase> Initialize(IClient client) {
var item = new CityFilter("City Filters");
item.Filters.Add(new CityBase());
return item;
}
}
And then we can test it:
static void Main(string[] args) {
IClient _client;
_client = null;
var filters = new List<IFilterSetManager>() {
new CityManager(),
new ChainManager()
};
foreach (var item in filters) {
var newFilter = item.Initialize(_client);
System.Console.WriteLine("Filter name: " + newFilter.FilterName);
System.Console.WriteLine("Filters added: " + newFilter.Filters.Count);
}
System.Console.ReadLine();
}

WF4: How do I evaluate an expression only known at runtime?

I am trying to create a simple WF4 activity that accepts a string that contains a VB.NET expression (from say the database), evaluates that string using the variables available in the current scope of the workflow and returns the result. Unfortunately, with the ways I've tried it, whether it be with a plain on Activity or a full-fledged NativeActivity, I keep hitting a wall.
My first attempt was with a simple Activity, and I was able to make a simple class that evaluates an expression given some object as its input:
public class Eval<T, TResult> : Activity<TResult>
{
[RequiredArgument]
public InArgument<T> Value { get; set; }
public Eval(string predicate)
{
this.Implementation = () => new Assign<TResult>
{
Value = new InArgument<TResult>(new VisualBasicValue<TResult>(predicate)),
To = new ArgumentReference<TResult>("Result")
};
}
public TResult EvalWith(T value)
{
return WorkflowInvoker.Invoke(this, new Dictionary<string, object>{ {"Value", value } });
}
}
This woks nicely, and the following expression evaluates to 7:
new Eval<int, int>("Value + 2").EvalWith(5)
Unfortunately, I can't use it the way I want since the expression string is given as a constructor argument instead of as an InArgument<string>, so it can't be easily incorporated (dragged and dropped) into a workflow. My second attempt was to try and use NativeActivity to get rid of that pesky constructor parameter:
public class NativeEval<T, TResult> : NativeActivity<TResult>
{
[RequiredArgument] public InArgument<string> ExpressionText { get; set; }
[RequiredArgument] public InArgument<T> Value { get; set; }
private Assign Assign { get; set; }
private VisualBasicValue<TResult> Predicate { get; set; }
private Variable<TResult> ResultVar { get; set; }
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
base.CacheMetadata(metadata);
Predicate = new VisualBasicValue<TResult>();
ResultVar = new Variable<TResult>("ResultVar");
Assign = new Assign { To = new OutArgument<TResult>(ResultVar), Value = new InArgument<TResult>(Predicate) };
metadata.AddVariable(ResultVar);
metadata.AddChild(Assign);
}
protected override void Execute(NativeActivityContext context)
{
Predicate.ExpressionText = ExpressionText.Get(context);
context.ScheduleActivity(Assign, new CompletionCallback(AssignComplete));
}
private void AssignComplete(NativeActivityContext context, ActivityInstance completedInstance)
{
Result.Set(context, ResultVar.Get(context));
}
}
I tried running NativeEval with the following:
WorkflowInvoker.Invoke(new NativeEval<int, int>(), new Dictionary<string, object>
{ { "ExpressionText", "Value + 2" }, { "Value", 5 } });
But got the following exception:
Activity '1: NativeEval' cannot access this variable because it is declared at the scope of activity '1: NativeEval'. An activity can only access its own implementation variables.
So I changed metadata.AddVariable(ResultVar); to metadata.AddImplementationVariable(ResultVar); but then I got a different exception:
The following errors were encountered while processing the workflow tree:
'VariableReference': The referenced Variable object (Name = 'ResultVar') is not visible at this scope. There may be another location reference with the same name that is visible at this scope, but it does not reference the same location.
I tried using .ScheduleFunc() as described here to schedule a VisualBasicValue activity, but the result it returned was always null (but oddly enough no exceptions were thrown).
I'm stumped. The metaprogramming model of WF4 seems much more difficult than the metaprogramming model of System.Linq.Expressions, which albeit difficult and often perplexing (like metaprogramming usually is), at least I was able to wrap my head around it. I guess it's because it has the added complexity of needing to represent a persistable, resumable, asynchronous, relocatable program, rather than just a plain old program.
EDIT: Since I don't think the issue I'm experiencing is caused by the fact that I'm trying to evaluate an expression that isn't hardcoded, the following alteration can be made to the NativeActivity that cause it to have a static expression:
Replace
Predicate = new VisualBasicValue<TResult>();
With
Predicate = new VisualBasicValue<TResult>("ExpressionText.Length");
And remove the line
Predicate.ExpressionText = ExpressionText.Get(context);
Now even though with those lines the expression is static, I'm still getting the same errors.
EDIT2: This article addressed the exception I was getting. I had to change both variable and child activity to be an "implementation", so this:
metadata.AddVariable(ResultVar);
metadata.AddChild(Assign);
Changed to this:
metadata.AddImplementationVariable(ResultVar);
metadata.AddImplementationChild(Assign);
And caused all the exceptions to go away. Unfortunately, it revealed that the following line does absolutely nothing:
Predicate.ExpressionText = ExpressionText.Get(context);
Changing the ExpressionText property of a VisualBasicValue during runtime has no effect. A quick check with ILSpy reveals why - the expression text is only evaluated and converted to an expression tree when CacheMetadata() is called, at which point the expression is not yet know, which is why I used the parameterless constructor which initialized and crystallized the expression to a no-op. I even tried saving the NativeActivityMetadata object I got in my own CacheMetadata overridden method and then use reflection to force a call to VisualBasicValue's CacheMetadata(), but that just ended up throwing a different cryptic exception ("Ambiguous match found." of type AmbiguousMatchException).
At this point it doesn't seem possible to fully integrate a dynamic expression into a workflow, exposing all the in-scope variables to it. I guess I'll have the method used in my Eval class within the NativeEval class.
I ended up using the following activity. It can't access the workflow's variables, instead it accepts a single argument 'Value' that can be used by the same name inside the dynamic expression. Other than that it works pretty well.
public class Evaluate<TIn, TOut> : NativeActivity<TOut>
{
[RequiredArgument]
public InArgument<string> ExpressionText { get; set; }
[RequiredArgument]
public InArgument<TIn> Value { get; set; }
protected override void Execute(NativeActivityContext context)
{
var result = new ExpressionEvaluator<TIn, TOut>(ExpressionText.Get(context)).EvalWith(Value.Get(context));
Result.Set(context, result);
}
}
public class ExpressionEvaluator<TIn, TOut> : Activity<TOut>
{
[RequiredArgument]
public InArgument<TIn> Value { get; set; }
public ExpressionEvaluator(string predicate)
{
VisualBasic.SetSettingsForImplementation(this, VbSettings);
Implementation = () => new Assign<TOut>
{
Value = new InArgument<TOut>(new VisualBasicValue<TOut>(predicate)),
To = new ArgumentReference<TOut>("Result")
};
}
public TOut EvalWith(TIn value)
{
return WorkflowInvoker.Invoke(this, new Dictionary<string, object> { { "Value", value } });
}
private static readonly VisualBasicSettings VbSettings;
static ExpressionEvaluator()
{
VbSettings = new VisualBasicSettings();
AddImports(typeof(TIn), VbSettings.ImportReferences);
AddImports(typeof(TOut), VbSettings.ImportReferences);
}
private static void AddImports(Type type, ISet<VisualBasicImportReference> imports)
{
if (type.IsPrimitive || type == typeof(void) || type.Namespace == "System")
return;
var wasAdded = imports.Add(new VisualBasicImportReference { Assembly = type.Assembly.GetName().Name, Import = type.Namespace });
if (!wasAdded)
return;
if (type.BaseType != null)
AddImports(type.BaseType, imports);
foreach (var interfaceType in type.GetInterfaces())
AddImports(interfaceType, imports);
foreach (var property in type.GetProperties())
AddImports(property.PropertyType, imports);
foreach (var method in type.GetMethods())
{
AddImports(method.ReturnType, imports);
foreach (var parameter in method.GetParameters())
AddImports(parameter.ParameterType, imports);
if (method.IsGenericMethod)
{
foreach (var genericArgument in method.GetGenericArguments())
AddImports(genericArgument, imports);
}
}
if (type.IsGenericType)
{
foreach (var genericArgument in type.GetGenericArguments())
AddImports(genericArgument, imports);
}
}
}
EDIT: Updated the class to include complete assembly and namespace imports, lest you get the dreaded (and unhelpful) error message:
'Value' is not declared. It may be inaccessible due to its protection level.
Also, moved the ExpressionEvaluator class outside and made it public, so you can used it outside of WF, like so:
new ExpressionEvaluator<int, double>("Value * Math.PI").EvalWith(2);
Which will return:
6.28318530717959
I would suggest to use a different framework for this. One good approach is to use nCalc. http://ncalc.codeplex.com/
It can parse any expression and evaluate the result, including static or dynamic parameters and custom functions.
We use it to evaluate different kind of expressions at runtime.
If your 'predicate' is a well-known string and don't need to be an expression evaluated at runtime you surely can do something like this, throwing away the InArgument and avoid the constructor:
public class Eval<T, TResult> : Activity<TResult>
{
public string Expression { get; set; }
[RequiredArgument]
public InArgument<T> Value { get; set; }
protected override Func<Activity> Implementation
{
get
{
if (string.IsNullOrEmpty(Expression))
{
return base.Implementation;
}
return () => new Assign<TResult>
{
Value = new InArgument<TResult>(new VisualBasicValue<TResult>(Expression)),
To = new ArgumentReference<TResult>("Result")
};
}
set
{
throw new NotSupportedException();
}
}
}
and call it this way:
var activity = new Eval<int, int>() { Expression = "Value + 2" };
var inputArgs = new Dictionary<string, object>()
{
{ "Value", 5 }
};
Console.WriteLine("RESULT: " + WorkflowInvoker.Invoke<int>(activity, inputArgs));
EDIT: check that even with Predicate.ExpressionText not commented, it has no effect whatsoever:
public class NativeEval<T, TResult> : NativeActivity<TResult>
{
[RequiredArgument]
public InArgument<string> ExpressionText { get; set; }
[RequiredArgument]
public InArgument<T> Value { get; set; }
private Assign Assign { get; set; }
private VisualBasicValue<TResult> Predicate { get; set; }
private Variable<TResult> ResultVar { get; set; }
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
base.CacheMetadata(metadata);
Predicate = new VisualBasicValue<TResult>("ExpressionText.Length");
ResultVar = new Variable<TResult>("ResultVar");
Assign = new Assign { To = new OutArgument<TResult>(ResultVar), Value = new InArgument<TResult>(Predicate) };
metadata.AddImplementationVariable(ResultVar);
metadata.AddImplementationChild(Assign);
}
protected override void Execute(NativeActivityContext context)
{
// this line, commented or not, is the same!
Predicate.ExpressionText = ExpressionText.Get(context);
context.ScheduleActivity(Assign, new CompletionCallback(AssignComplete));
}
private void AssignComplete(NativeActivityContext context, ActivityInstance completedInstance)
{
// the result will always be the ExpressionText.Length
Result.Set(context, ResultVar.Get(context));
}
}
When you get at Execute() method changing the child implementation has no effect. The execution mode is on and the children tree cannot be altered.

Generic Class to create a Generic Collection using IEnumerable<T>

This is a Two (2) Part Question about Generics
I've got to create several similar classes to model similarly designed database tables.
All tables contain an ID int and a Text nvarchar(50) field. One or two may contain a few other fields.
I rarely use generics, but I see examples of it on here quite frequently. This is my largest attempt to create a generic class that is used in another generic class.
My basic construct is as follows, and I will point out with a comment what does not work and the error message Visual Studio 2010 is displaying:
public class IdText {
public IdText(int id, string text) {
ID = id;
Text = text;
}
public int ID { get; private set; }
public string Text { get; private set; }
}
public class TCollection<T> : IEnumerable<T> where T : IdText {
private List<T> list;
public TCollection() {
list = new List<T>();
}
public void Add(int id, string text) {
foreach (var item in list) {
if (item.ID == id) {
return;
}
}
list.Add(new T(id, text)); // STOP HERE
// Cannot create an instance of the variable type 'T'
// because it does not have the new() constraint
}
public T this[int index] {
get {
if ((-1 < 0) && (index < list.Count)) {
return list[index];
}
return null;
}
}
public T Pull(int id) {
foreach (var item in list) {
if (item.ID == id) {
return item;
}
}
return null;
}
public T Pull(string status) {
foreach (var item in list) {
if (item.Text == status) {
return item;
}
}
return null;
}
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator() {
foreach (var item in list) yield return item;
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
return list.GetEnumerator();
}
#endregion
}
Visual Studio's IntelliSence wants me to add list.Add(T item), but I need to create this first.
I have attempted to re-write the offending line list.Add(new T(id, text)); as list.Add(new IdText(id, text));, but then I am reprimanded with the message "cannot convert from IdText to T".
How exactly do I get around this?
Next: When I go in to actually create a version of this IdText class later, I am not sure how exactly I can use this new class in the TCollection class I have designed for it.
For example, given this derived class:
public class ManufacturedPart : IdText {
public ManufacturedPart(int id, string partNum, string description)
: base(int id, string partNum) {
Description = description;
}
public string Description { get; private set; }
}
...would I need to also derive a special version of TCollection to accompany it, like so?
public class ManufacturedParts<T> : IEnumerable<T> where T : ManufacturedPart {
// OK, now I'm lost! Surely this can't be right!
}
1) You could use the new() constraint, make your properties public and add a parameterless constructor:
public class IdText
{
public IdText()
{
}
public IdText(int id, string text)
{
ID = id;
Text = text;
}
public int ID { get; set; }
public string Text { get; set; }
}
public class TCollection<T> : IEnumerable<T> where T : IdText, new()
{
private List<T> list;
public TCollection()
{
list = new List<T>();
}
public void Add(int id, string text)
{
foreach (var item in list)
{
if (item.ID == id)
{
return;
}
}
list.Add(new T { ID = id, Text = text });
}
}
2) You have multiple options:
If you want your collection to store any IdText (ManufacturedPart or anything else that derived from IdText):
TCollection<IdText> ss = new TCollection<IdText>();
The above, for now, can only store IdText as you instantiate objects in the Add(int, string) method, but if you provide a Add(T object) method, it could store any IdText instance.
If you want your collection to only contains ManufacturedParts:
public class ManufacturedParts<T> : TCollection<T> where T : ManufacturedPart, new()
{
// Provide here some specific implementation related to ManufacturedParts
// if you want. For example, a TotalPrice property if ManufacturedPart
// has a Price property.
}
TCollection<ManufacturedPart> ss2 = new ManufacturedParts<ManufacturedPart>();
or even simpler, if your collection doesn't provide any additional method depending on the type of the stored objects:
TCollection<ManufacturedPart> ss2 = new TCollection<ManufacturedPart>();
Even simpler, if your goal is to only store objects, a custom collection isn't needed:
List<IdText> ss2 = new List<IdText>(); // Uses the built-in generic List<T> type
About the first question: c# doesn't support constructors with parameters as a generic constrain. Something you can do is replace it with
(T)Activator.CreateInstance(typeof(T),new object[]{id,text});
By the other hand... you don't actually know how the constructors of the derived class will look like, so you can't ensure they will have that constructor.
About the second question, you can do this:
var collection = new TCollection<ManufacturedPart>();
in the same way List works.
Hope it helps.
If your collection class will be responsible for instantiating elements of its collected type, then you probably don't want to be using either the new() constraint or Activator.CreateInstance() -- as Jon Skeet has blogged, both of these exhibit poor performance.
It sounds like what you actually want is a provider delegate, like so:
public class MyCollection<T> : IEnumerable<T> where T : IdText {
private readonly List<T> list;
private readonly Func<int, string, T> provider;
public MyCollection(Func<int, string, T> provider) {
this.list = new List<T>();
this.provider = provider;
}
public void Add(int id, string text) {
list.Add(provider(id, text));
}
}
And then you'd use it like:
var collection = new MyCollection((id, text) => new ManufacturedPart(id, text));
You can think of this as passing the specific constructor you want to use as an argument into the class, which it then uses to construct instances as needed.
And you don't need to create a separate subclass for MyCollection<ManufacturedPart> -- just use the generic class directly.

Generic class used as constraint to generic method in C#?

Am I doing something wrong or is it not possible to specify a generic class as a constraint to a generic method?
I have been playing around with generics and db4o (open source object database) and am writing a test program (see code below) to store and retrieve some user defined generic collections.
I am attempting to write a generic method (see GetCollectionFromDb below) to retrieve a specifically typed collection from the database. Unfortunately, the code below returns a compiler generated error for the line:
MyCollection1 collection3 =
GetCollectionFromDb<MyCollection1>(Collection1Name);
The error message is:
The type 'GenericsTest.MyCollection1'cannot be used as type parameter 'T'
in the generic type or method 'GenericsTest.Program.GetCollectionFromDb<T>(string)'.
There is no implicit reference conversion from'GenericsTest.MyCollection1' to
'GenericsTest.MyCollectionBase<GenericsTest.MyCollection1>'.
I would appreciate any suggestions as to what I may be doing wrong or how I could approach this differently to reach the deisred outcome.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Db4objects.Db4o;
namespace GenericsTest
{
public class Entity1
{
public string SomeProperty { get; set; }
}
public class Entity2
{
public string SomeProperty { get; set; }
}
public abstract class MyCollectionBase<T> : Collection<T>
{
protected MyCollectionBase() : this("") { }
protected MyCollectionBase(string pCollectionName)
{
CollectionName = pCollectionName;
}
public string CollectionName { get; set; }
}
public class MyCollection1 : MyCollectionBase<Entity1>
{
public MyCollection1(string pCollectionName) :
base(pCollectionName) { }
public void DoSomeWorkOnCollection1() {}
}
public class MyCollection2 : MyCollectionBase<Entity2>
{
public MyCollection2(string pCollectionName) :
base(pCollectionName) { }
public void DoSomeWorkOnCollection2() { }
}
public class Program
{
public static IObjectContainer db = null;
public static void Main(string[] args)
{
const string Collection1Name = "Entity1Collection";
const string Collection2Name = "Entity2Collection";
db = Db4oFactory.OpenFile("Test.db");
Entity1 entity1 = new Entity1();
MyCollection1 collection1 = new MyCollection1(Collection1Name);
collection1.Add(entity1);
db.Store(collection1);
Entity2 entity2 = new Entity2();
MyCollection2 collection2 = new MyCollection2(Collection2Name);
collection1.Add(entity1);
db.Store(collection2);
db.Commit();
db.Close();
db = Db4oFactory.OpenFile("Test.db");
MyCollection1 collection3 =
GetCollectionFromDb<MyCollection1>(Collection1Name);
}
private static T GetCollectionFromDb<T>(string pCollectionName)
where T : MyCollectionBase<T>
{
IList<T> queryResult = db.Query((T c) =>
c.CollectionName == pCollectionName);
if (queryResult.Count != 0) return queryResult[0];
return null;
}
}
}
Your type doesn't satisfy the constraint. You've supplied MyCollection1 which derives from MyCollection<Entity1>. However, that doesn't mean it derives from MyCollection<MyCollection1>.
Perhaps you want to express the constraint over two type parameters instead of one:
private static T GetCollectionFromDb<T, U>(string pCollectionName)
where T : MyCollectionBase<U>
Then call it with:
GetCollectionFromDb<MyCollection1, Entity1>(Collection1Name);
If that doesn't do the trick, please let us know why.
Just follow the T:
// ...
{
//...
MyCollection1 collection3 = GetCollectionFromDb<MyCollection1>(Collection1Name);
}
private static T GetCollectionFromDb<T>(string pCollectionName) where T : MyCollectionBase<T>
{
IList<T> queryResult = db.Query((T c) => c.CollectionName == pCollectionName);
if (queryResult.Count != 0) return queryResult[0];
return null;
}
would translate into:
private static MyCollection1 GetCollectionFromDb<MyCollection1>(string pCollectionName) where T : MyCollectionBase< MyCollection1 >
{
IList< MyCollection1 > queryResult = db.Query((MyCollection1 c) => c.CollectionName == pCollectionName);
if (queryResult.Count != 0) return queryResult[0];
return null;
}
Which is not what you want since MyCollection1 derives off MyCollectionBase< Entity1 > and not MyCollectionBase< MyCollection1 >, which is why you got the error. If you want the constraint to work, you will probably have to use a second type identifier to express the type being used in the generic collection.

Categories