Hi,
Peter said:
Perhaps you could clarify what you mean. System.Windows.Forms.DataGrid
is a GUI class, dealing in things that a user can understand, such as
strings.
well, I would like to call it HTML, which can be by far more than string
(although there is a biunique conversion to string).
You can abstract a database with a strongly-typed class by using the
database features in VS to create a database adapter that does that.
First of all I have no database. At least no database, that can hold the
OO data model natively in any way. So the adapter would not be helpful.
Secondly, generated typed datasets are one of the slowest things I have
ever seen. Whereever we used them, they degraded application performance
compared to individual data types significantly. Especially the memory
footprint is horrible. I prefer not to see them in production code where
performance counts in any way. Last but not least they are restricted to
relational data models.
Thirdly, the user wants to operate on results of joins over different
entities. So I would have to write a new data type for each bindable
result in the application. I really do not want to do so.
Last but not least, I do not restrict the column content to anything
similar to strings. Instead depending on the data type complex controls
(sometimes with wizards) will bind to complex non flat data objects like
arrays or even class objects.
What would your proposed generic design for the DataGrid class be?
I have wrapped the Grid with a strongly typed version which has a
bindable DataSource for the X and the Y direction. The X data source is
a list of column descriptors that feed an (overloadable) column factory.
The column descriptors must be references to properties of the element
type of the Y data source.
I don't see how the addition of a new feature to the language should be
considered to _break_ design.
Of course, the addition of the generics breaks nothing. However, when
using template meta programming consequently, almost any actions that
are constant with respect to the resulting binary is moved from run time
to compile time. Small lacks in the language features inhibit this
programming style.
And I think using generics in the context
of a data grid object of any sort would be very unwieldy (a separate
generic type for each possible number of columns and one type parameter
per column? I'd rather not),
Of course, not.
// Any property of an object type O
interface IPropertyRef<O>
{ ... }
interface IColumnFactory
{
GridColumn CreateColumn<O>(IPropertyRef<O> prop);
}
class Grid<O>
{
public IEnumerable<O> DataSource { get; set; } // Y data source
public IEnumerable<IPropertyRef<O>> XDataSource { get; set; }
public IColumnFactory ColumnFactory { get; set; }
}
Currently I have the meta data completely separated from the data
objects. The meta data is stored in a singleton class for each entity.
These classes have fields for each property of the entity. The data
objects itself are stupid. They only have a generic get and set method
that can be invoked with a property descriptor of the matching meta data
class. These methods provide O(1) access to the data. Furthermore the
data objects have a TypeDescriptorProvider to keep the DataGrid happy.
[TypeDescriptionProvider(typeof(AnyObjectTypeDescriptionProvider))]
class AnyObject<E> where E : EntityType
{
// element access
// Unfortunately this is no valid code. Indexers cannot be generic.
// However, this is what actually happens with an explicit
// Get and Set method as work around.
public T this<T>[EntityAttribute<E,T> key]
{ get {...}
set {...}
}
}
// Meta data of an entity
class EntityType
{...}
// Meta data of a property of an entity.
class EntityProperty<E,T> : IPropertyRef<AnyObject<E>>
where E : EntityType
{...}
class CustomerType : EntityType
{
public static EntityProperty<CustomerType, int> Age
= new EntityProperty<CustomerType, int>();
public static EntityProperty<CustomerType, Address[]> Addresses
= new EntityProperty<CustomerType, Address[]>();
...
}
class Address
{
public string Street { get; set; }
...
}
AnyObject<CustomerType> obj = ...
// Element access must be .Get/.Set in real life. See above.
int age = obj[CustomerType.Age];
obj[CustomerType.Age] = 17;
myGrid.XDataSource = new IPropertyRef<AnyObject<CustomerType>>[]
{ CustomerType.Age,
...
}
This concept fulfills all my requirements, since the EntityProperty
instances serve as references to properties.
Unfortunately this is mutually exclusive with the .NET concept to bind
to properties via reflection. So if I need to join these completely
generic entity objects with simple class objects with public properties
(e.g. Address) I do no longer have a type safe way to access the
properties of both entities in one Grid. It only works as long as I join
only over my own entities. This would mean in the above example that I
have to turn Address[] into AnyObject<AddressType>[]. This is an very
inconvenient implication. Currently only strong entities are kept in the
above way.
as well as not really solve the fundamental
problems anyway (binding would still have to be done through converters,
and general-purpose property names would be less useful than passing
string names to a column indexer).
?
What do yo call general-purpose property names?
Do you mean "return this.Age"?
Oops, of course.
How is your proposal different from
using reflection and a PropertyInfo object?
In no way. Except that using reflections has all the disadvantages that
the DataBinder solution shares too.
If you actually want a delegate instance, what's so wrong with using
real delegates?
Real delegates cannot be taken from properties (or I did not find out
the right syntax). And furthermore they do not support to be applied to
a different object than the one which was used to create them. The
latter is a show stopper. The above pseudo code is an ugly (and
incomplete) work-around.
[...]
Unfortunately I did not found a way to get something like the above
reference from the property Customer.Age. Of course, one could use lamda
expressions all over the world.
"All over the world"? Seems to me, you'd have to pass appropriate
delegate instances during the initialization of your envisioned DataGrid
variant, and that would be that.
Yes, but there are many Grids. Furthermore the same binding scheme also
applies to single controls. These Controls could be generated by a
repeater over the meta data of a type and a control factory. This will
generate a fully functional editor for any data type, even if its
properties are not only basic data types. Of course, a user or
customizing data may choose, which properties can be accessed in a view.
Why does your example declare "SetProperty" with a Func<T, TResult>, but
then apparently use the method with an Action<T>?
Where do you see Action<T>? (Customer cust) => cust.Age is of type
Func said:
Wouldn't Action<T> be
more appropriate in the declaration?
No. At the point of the delegate definition (i.e. column creation) I do
not have an object of type customer. The function has to be applied to
each data row.
And what are "O" and "T" in that
declaration?
O := row type
T := property type
What drawbacks are you trying to avoid that you think that your proposed
feature would solve?
First of all: type safety. Not /that/ important: performance.
Given your problem description (vague as it is),
it seems that what you're really asking for is exactly what reflection
provides: an object (i.e. PropertyInfo) that provides an abstraction of
the property getter and setter that can be used at any time later.
Reflection is necessarily not type-safe at compile time, but it's
trivial to cast at run-time assuming the code can know the type (and if
it can't, you couldn't use generics anyway).
Hmm, wrapping the PropertyDescriptor type safely could be an option.
You could instead use lambda expressions to create type-safety at
compile time, and there's nothing about your problem description that
makes clear to me why you would need to specify the expression more than
once in your code.
It should be able to be used to create a delegate
instance on initialization, where that instance is then reused at any
point later where you need the access you're looking for.
OK, I could hold a delegate for each property in a central location,
rather than at any place where I refer to a property. Well, two
delegates for each property, since I need a setter too and a .NET
function cannot return something like "ref T".
Writing entity types becomes somewhat like child labor, because each
property needs at least 3 consistent objects (the property and the two
delegates).
But fundamentally, one major problem here is that your problem
description is vague. You didn't post any sort of complete code example
that would show exactly what you mean,
Well, the complete OR mapper code is large. And it consists of several
things that do not belong to this topic, like emulated constness and
sharing of const objects between threads. Making a minimal working
example is several hours of work.
There are a few lines of code in your question that hint at
what you really want, but those sketches aren't enough to really explain
what you mean.
You are right. I am sorry for inconvenience. Hopefully I could improve
that. But maybe I only raised even more questions.
Thanks for your reply. I will rethink the tip with the static delegates.
Putting the getter and setter together in a struct will give me
something like a type safe reference to a property.
But it would be really helpful if the .NET language would support to
initialize a delegate type from a property. I am almost sure that this
is nearly a no-op in IL code, since the required methods get_Property
and set_Property already exist. There is only no syntax to access them
explicitly.
Marcel