Reputation: 1579
So, I've found several answers to half of my issue, but the two pieces I am trying to combine seem to put things together in ways that others haven't run into before (or at least they're not posting about it).
My objective is to be able use my code like this:
ModelFactory[DataModelX].DataY
What I know I can get is this:
ModelFactory.Get<DataModelX>.DataY
Admittedly not that much different, but at this point I've felt that I've gotten close and want to see if I can get it.
My current classes are:
public class ModelFactory<T> : : IReadOnlyList<IDataModel> where T : IDataModel
private static ModelFactory<IDataModel> _instance;
private ModelFactory() {}
public static ModelFactory<IDataModel> ModelFactory => _instance ?? (_instance = new DataModelFactory<IDataModel>());
private readonly List<IDataModel> _models = new List<IDataModel>();
private T GetHelper(Type type)
{
// check if model exists
foreach (var model in _models.Where(model => model.GetType() == type))
{
// model exists, return it
return (T) model;
}
// model doesn't exist, so we need to create it
var newModel = CreateModel(type);
_models.Add(newModel);
return newModel;
}
public T this[
Type type
] {
get
{
return GetHelper(type);
}
}
And DataModels like so:
public class DataModel1 : IDataModel
public string Alpha {get; set;}
public int Beta {get; set;}
// etc...
public class DataModel2 : IDataModel
public foo Cat {get; set;}
public foobar Dog {get; set;}
// etc...
And so on.
However, when I try to type ModelFactory.ModelFactory[DataModelX].
I can't get access to ModelFactory. The property is obviously public, and contained within the appropriate class. Also, I can't pass the class as a parameter (of course), but I don't know how to adjust the parameter on the array indexing to accept the class as a parameter.
Constaints:
Upvotes: 0
Views: 308
Reputation: 112279
You can use typeof(T)
in order to get the type description of the generic parameter T
.
T newModel = (T)CreateModel(typeof(T));
There is no need to have an additional Type parameter, if it always describes the same type as T
.
Also you could use a dictionary instead of a list for storing models.
private readonly Dictionary<Type,IDataModel> _models = new Dictionary<Type,IDataModel>();
Then you can get a value with
IDataModel model;
If (!_models.TryGetValue(typeof(T), out model)) {
model = CreateModel(typeof(T));
_models.Add(typeof(T), model);
}
return (T)model;
The generic type parameter of your class is misleading as it seems to imply that you are going to create one factory per model type. Just drop it.
Also you cannot implement IReadOnlyList<out T>
as it requires the index to be an int
. Drop it.
public class ModelFactory { ... }
Now you have two options:
Let the indexer or an ordinary Get-method return a IDataModel
and later cast the result
var m = (MyModelType)factory.Get(typeof(MyModelType));
.
or
var m = (MyModelType)factory[typeof(MyModelType)];
.
Declare a get method with a generic type argument (but no generic type argument for the class!). Indexers cannot have generic type arguments.
public T Get<T>() : where T : IDataModel
{
... (same as above)
return (T)model;
}
call it with
var m = factory.Get<MyModelType>();
Upvotes: 2
Reputation: 743
While it would be nice to have, what you are asking for is unfortunately impossible.
There are three possible ways of implementing this factory, none of which do what you want:
1) The implementation you have, which is a generic ModelFactory<T> where T : IDataModel
. As has been mentioned, a ModelFactory<T>
would force you to limit your factory to take a single type - as in, you would have to define a ModelFactory<DataModel1>
, a ModelFactory<DataModel2>
, etc. That's a questionable design decision anyway, but more to the point, you can only ever put in and get out the concrete IDataModel
type you used to create the factory. For example, you can't create a ModelFactory<DataModel1>
and get a DataModel2
from the indexer. So there's not much point to using this implementation.
2) A non-generic ModelFactory
with an indexer that returns an IDataModel
. This is perfectly valid, but you would have to cast the output to get at the concrete type. So your call syntax using the indexer would be ((DataModel2)ModelFactory[DataModel2]).Cat
, or (ModelFactory[DataModel2] as DataModel2).Cat
. In my opinion, this is a LOT uglier than a generic Get<T>()
function.
3) A non-generic ModelFactory
with a generic indexer, such as public T this<T>[Type type] where T : IDataModel
. This is closest to what you want, but unfortunately, it is explicitly disallowed under current (C# 6) rules. See, for example, Why it is not possible to define generic indexers in .NET?. It's worth noting, however, that you still wouldn't be able to get the compiler to automatically recognize type
as T
. You couldn't write public T this<T>[T type]
because that would interpret type
as an object of type T
, nor could you write public T this<T>[typeof(T) type]
or something like it, since typeof(T)
is an object of type Type
. So you would have to call your indexer with ModelFactory<DataModel2>[typeof(DataModel2)].Cat
(still ugly!), and implement a runtime check inside to ensure that type
is really type T
.
So the cleanest option you have is to use a non-generic ModelFactory
that retrieves IDataModel
implementations from an internal Dictionary<Type, IDataModel>
, using public T Get<T>() where T : IDataModel { ... }
.
Upvotes: 1