Type convertsion from string

A

Andrus

Using VCS 2005 Express .NET 2 WinForms.

I implemented entity databinding in DataGridView OnCellValuePushed method as

PropertyInfo p = entityTmp.GetType().GetProperty(
Columns[e.ColumnIndex].DataPropertyName);
// e.Value type is string
p.SetValue(entityTmp, e.Value, null);

This works only for string property types.
For other types SetValue() causes System.ArgumentException like

Object of type 'System.String' cannot be converted to type 'System.Decimal'.

How to change this code so that SetValue() works for non-string types ?
I need to convert string type (e.Value) to property type:

How to convert string to property p type ?

Andrus.
 
J

Jon Skeet [C# MVP]

Andrus said:
Using VCS 2005 Express .NET 2 WinForms.

I implemented entity databinding in DataGridView OnCellValuePushed method as

PropertyInfo p = entityTmp.GetType().GetProperty(
Columns[e.ColumnIndex].DataPropertyName);
// e.Value type is string
p.SetValue(entityTmp, e.Value, null);

This works only for string property types.
For other types SetValue() causes System.ArgumentException like

Object of type 'System.String' cannot be converted to type 'System.Decimal'.

How to change this code so that SetValue() works for non-string types ?
I need to convert string type (e.Value) to property type:

How to convert string to property p type ?

Have you tried Convert.ChangeType?
 
A

Andrus

Have you tried Convert.ChangeType?

Jon,

thank you. I changed second line to

p.SetValue(entityTmp, Convert.ChangeType(e.Value, p.PropertyType), null);

and this works.

When user enters letters to numeric cell, this line causes Exception.
How to check that conversion can be performed ?
Should I use try catch to catch this exception or is it possible to
determine conversion error before raising exception ?

Andrus.
 
J

Jon Skeet [C# MVP]

Andrus said:
thank you. I changed second line to

p.SetValue(entityTmp, Convert.ChangeType(e.Value, p.PropertyType), null);

and this works.

When user enters letters to numeric cell, this line causes Exception.
How to check that conversion can be performed ?
Should I use try catch to catch this exception or is it possible to
determine conversion error before raising exception ?

I don't think there's any equivalent of "TryChangeType", although you
might want to encapsulate that in a method yourself to avoid polluting
the rest of your code.

Others may have better suggestions though :)
 
M

Marc Gravell

Actually, to allow the widest set of usage and conversions, you should
be using PropertyDescriptor (not PropertyInfo); not all "property"
implementations are actually class properties. Think (non-typed)
DataRow, although many other examples (involving ICustomTypeDescriptor
or TypeDescriptionProvider) exist.

For the conversion, you might want to look at TypeConverter usage,
specifically (where p is a PropertyDescriptor):

ITypeDescriptorContext ctx = new SimpleContext(entityTmp,
p);
if (p.Converter.CanConvertFrom(ctx, typeof(string))) {
object val = p.Converter.ConvertFromString(ctx,
e.Value);
p.SetValue(entityTmp, val);
} else {
// something
}

where SimpleContext is something like:

[ImmutableObject(true)]
public class SimpleContext : ITypeDescriptorContext {
private readonly object instance;
private readonly PropertyDescriptor property;
public SimpleContext(object instance, PropertyDescriptor
property) {
this.instance = instance;
this.property = property;
}
public IContainer Container {get { return null; }}
public object Instance {get { return instance; }}
public void OnComponentChanged() {}
public bool OnComponentChanging() {return true;}
public PropertyDescriptor PropertyDescriptor { get { return
property; } }
public object GetService(Type serviceType) {return null;}
}

This does 3 things that Convert.ChangeType does not:
* respects a TypeConverter specified on the property itself (rather
than the type)
* passes context to the converter, which is sometimes required if the
converter uses property metadata
* (by doing the above) more closley represents how DataGridView (and
many other bindings) themselves work

There are corresponding .ConvertTo() methods for getting the string in
the first place, and an .IsValid() method for verifying values [but
this is really intended for known-list (StandardValues) lookups]. To
try the conversion I would simply catch the exception (if one). You'd
want to wrap the SetValue() too, since the property-setter could just
as legitimately throw an exception for any reason it likes.

Marc
 
A

Andrus

Marc,

Thank you.
I created the following method but this causes error shown in comment.
How to fix ?
Is this best style to create this method for conversion user entered value ?

Andrus.

/// <summary>
/// Set value of property from string.
/// </summary>
/// <param name="obj">Object whose property should be set</param>
/// <param name="propertyName">Name of property to set</param>
/// <param name="propertyValue">User entered value</param>
public static void SetValue(object obj, string propertyName, string
propertyValue) {

//How to fix error in following line: 'System.Type' does not contain a
definition for 'GetPropertyDescriptor'
PropertyDescriptor p = obj.GetType().GetPropertyDescriptor();

ITypeDescriptorContext ctx = new SimpleContext(obj,p);
if (p.Converter.CanConvertFrom(ctx, typeof(string))) {
object val = p.Converter.ConvertFromString(ctx, propertyValue);
p.SetValue(obj, val);
}
else {
UIManager.OKGet( "Invalid entry");
}
}

[ImmutableObject(true)]
public class SimpleContext : ITypeDescriptorContext {
private readonly object instance;
private readonly PropertyDescriptor property;

public SimpleContext(object instance, PropertyDescriptor
property) {

this.instance = instance;
this.property = property;
}

public IContainer Container {get { return null; }}
public object Instance {get { return instance; }}
public void OnComponentChanged() {}
public bool OnComponentChanging() {return true;}
public PropertyDescriptor PropertyDescriptor { get { return
property; } }

public object GetService(Type serviceType) {return null;}
}}

Marc Gravell said:
Actually, to allow the widest set of usage and conversions, you should
be using PropertyDescriptor (not PropertyInfo); not all "property"
implementations are actually class properties. Think (non-typed)
DataRow, although many other examples (involving ICustomTypeDescriptor
or TypeDescriptionProvider) exist.

For the conversion, you might want to look at TypeConverter usage,
specifically (where p is a PropertyDescriptor):

ITypeDescriptorContext ctx = new SimpleContext(entityTmp,
p);
if (p.Converter.CanConvertFrom(ctx, typeof(string))) {
object val = p.Converter.ConvertFromString(ctx,
e.Value);
p.SetValue(entityTmp, val);
} else {
// something
}

where SimpleContext is something like:

[ImmutableObject(true)]
public class SimpleContext : ITypeDescriptorContext {
private readonly object instance;
private readonly PropertyDescriptor property;
public SimpleContext(object instance, PropertyDescriptor
property) {
this.instance = instance;
this.property = property;
}
public IContainer Container {get { return null; }}
public object Instance {get { return instance; }}
public void OnComponentChanged() {}
public bool OnComponentChanging() {return true;}
public PropertyDescriptor PropertyDescriptor { get { return
property; } }
public object GetService(Type serviceType) {return null;}
}

This does 3 things that Convert.ChangeType does not:
* respects a TypeConverter specified on the property itself (rather
than the type)
* passes context to the converter, which is sometimes required if the
converter uses property metadata
* (by doing the above) more closley represents how DataGridView (and
many other bindings) themselves work

There are corresponding .ConvertTo() methods for getting the string in
the first place, and an .IsValid() method for verifying values [but
this is really intended for known-list (StandardValues) lookups]. To
try the conversion I would simply catch the exception (if one). You'd
want to wrap the SetValue() too, since the property-setter could just
as legitimately throw an exception for any reason it likes.

Marc
 
A

Andrus

Marc,

Thank you.

If I enter characters to numeric column I get exception "eee is not a valid
value for Decimal." at line

object val = p.Converter.ConvertFromString(ctx, propertyValue);
How to fix ?

Why CanConvertFrom() returns true ?

I want to allow users to add calculated columns to table at runtime.
Is it possible to add new calculated property to object at runtime or is
there any other solution ?

Andrus.

Exception which I got:

System.Exception was unhandled
Message="eee is not a valid value for Decimal."
Source="System"
StackTrace:
at
System.ComponentModel.BaseNumberConverter.ConvertFrom(ITypeDescriptorContext
context, CultureInfo culture, Object value)
at
System.ComponentModel.TypeConverter.ConvertFromString(ITypeDescriptorContext
context, String text)
at My.Business.StringConverter.SetValue(Object obj, String
propertyName, String propertyValue)
at
My.Windows.Forms.VirtualGrid`1.OnCellValuePushed(DataGridViewCellValueEventArgs
e)
at System.Windows.Forms.DataGridView.OnCellValuePushed(Int32
columnIndex, Int32 rowIndex, Object value)
at System.Windows.Forms.DataGridViewCell.SetValue(Int32 rowIndex,
Object value)
at
System.Windows.Forms.DataGridView.PushFormattedValue(DataGridViewCell&
dataGridViewCurrentCell, Object formattedValue, Exception& exception)
at System.Windows.Forms.DataGridView.CommitEdit(DataGridViewCell&
dataGridViewCurrentCell, DataGridViewDataErrorContexts context,
DataGridViewValidateCellInternal validateCell, Boolean fireCellLeave,
Boolean fireCellEnter, Boolean fireRowLeave, Boolean fireRowEnter, Boolean
fireLeave)
at
System.Windows.Forms.DataGridView.EndEdit(DataGridViewDataErrorContexts
context, DataGridViewValidateCellInternal validateCell, Boolean
fireCellLeave, Boolean fireCellEnter, Boolean fireRowLeave, Boolean
fireRowEnter, Boolean fireLeave, Boolean keepFocus, Boolean
resetCurrentCell, Boolean resetAnchorCell)
at System.Windows.Forms.DataGridView.CommitEditForOperation(Int32
columnIndex, Int32 rowIndex, Boolean forCurrentCellChange)
at System.Windows.Forms.DataGridView.ScrollIntoView(Int32
columnIndex, Int32 rowIndex, Boolean forCurrentCellChange)
at System.Windows.Forms.DataGridView.TabToNextCell()
at System.Windows.Forms.DataGridView.ProcessTabKey(Keys keyData)
at System.Windows.Forms.DataGridView.ProcessDialogKey(Keys keyData)
at System.Windows.Forms.Control.ProcessDialogKey(Keys keyData)
at System.Windows.Forms.TextBoxBase.ProcessDialogKey(Keys keyData)
at System.Windows.Forms.Control.PreProcessMessage(Message& msg)
....
 
M

Marc Gravell

If I enter characters to numeric column I get exception "eee is not
a valid
value for Decimal." at line

object val = p.Converter.ConvertFromString(ctx, propertyValue);
How to fix ?
The message seems pretty reasonable to me... I don't know what number
"eee" represents, either. What exactly do you want it to do? is this
hex? or bad user input? If the latter, catch the exception and tell
the user about it. If the former (hex) write a TypeConverter to do the
job.
Why CanConvertFrom() returns true ?
CanConvertFrom doesn't take the value - it simply indicates whether it
is reasonable to try, based purely on the types. Does it make *sense*
to go from a decimal to a string. In this case, yes the TypeConverter
is willing to have a try, but that doesn't guarantee that it will work
given illegal input.
I want to allow users to add calculated columns to table at runtime.
Yes it is. But you need to know a range of things from
System.ComponentModel. If this is a requirement, can I suggest
DataTable? It isn't ideal from many OO standpoints, but it supports
this out-of-the-box.

For dynamic property additions (this is *not* trivial), see:
http://groups.google.co.uk/group/mi...csharp/browse_thread/thread/f4d8bb5dcdb82a9f/

However, this doesn't even begin to discuss how you would parse the
expression, i.e. "Age * 2"... so again, I say: look at DataTable... or
Excel. This is not an easy area.

Marc
 
A

Andrus

Marc,
Yes it is. But you need to know a range of things from
System.ComponentModel. If this is a requirement, can I suggest DataTable?
It isn't ideal from many OO standpoints, but it supports this
out-of-the-box.

thank you very much.
I need to use static methods (from string, Math and other classes) in
expressions.
I havent found any way to use them from DataTable column Expression
property.
How to call static methods from ADO.NET expressions ?
For dynamic property additions (this is *not* trivial), see:
http://groups.google.co.uk/group/mi...csharp/browse_thread/thread/f4d8bb5dcdb82a9f/
However, this doesn't even begin to discuss how you would parse the
expression, i.e. "Age * 2"... so again, I say: look at DataTable... or
Excel. This is not an easy area.

I'm thinking about the following:

1. Expression are stored in SQL server table.
2. Before first access to every entity object application creates entity
class (.cs file) from table structure in database and adds expressions as
properties to this file.
3. Application compiles cs file and loads this file to memory.

This avoids writing custom TypeDescriptor and Expression parser and allows
to use full power of C# to write expressions.

Is this best solution ?

Or is it better to create custom type "ExpressionMethod" and custom type
converter which converts string to ExpressionMethod type (to assembly or to
IL code)?

Andrus.
 
A

Andrus

PropertyDescriptor p = TypeDescriptor.GetProperties(obj)[propertyName];

I have GetHashCode() methods in my entity objects.
The line above causes NullReferenceException since it forces GetHashCode()
call for new entity where id is not set yet when adding new row.

How to fix ?

Andrus.
Code to reproduce:



using System;

using System.ComponentModel;

using System.Reflection;

class Test {

static void Main() {

Customer k = new Customer();

SetVal.SetValue(k, "Name", "Marc");

}

}

class Customer {

string id, name;

public string Name {

get { return name; }

set { name = value; }

}

public override int GetHashCode() {

int hash = 57;

hash = 27 * hash * id.GetHashCode();

return hash;

}

}

class SetVal {

public static void SetValue(object obj, string propertyName, string
propertyValue) {

#if false

// this works OK:

PropertyInfo p = obj.GetType().GetProperty(propertyName);

p.SetValue(obj,

propertyValue.Trim().Length == 0 ? null :

Convert.ChangeType(propertyValue, p.PropertyType), null);

#else

// this causes NRE:

PropertyDescriptor p = TypeDescriptor.GetProperties(obj)[propertyName];

ITypeDescriptorContext ctx = new SimpleContext(obj, p);

if (p.Converter.CanConvertFrom(ctx, typeof(string))) {

object val;

val = p.Converter.ConvertFromString(ctx, propertyValue);

p.SetValue(obj, val);

}

#endif

}

[ImmutableObject(true)]

public class SimpleContext : ITypeDescriptorContext {

private readonly object instance;

private readonly PropertyDescriptor property;

public SimpleContext(object instance, PropertyDescriptor

property) {

this.instance = instance;

this.property = property;

}

public IContainer Container { get { return null; } }

public object Instance { get { return instance; } }

public void OnComponentChanged() { }

public bool OnComponentChanging() { return true; }

public PropertyDescriptor PropertyDescriptor {

get {

return

property;

}

}

public object GetService(Type serviceType) { return null; }

}

}
 
M

Marc Gravell

How to call static methods from ADO.NET expressions ?
You can't ;-p
You could write, buy, beg or borrow a parser that handles what you
need. A quick search reveals USPExpress Math Parser (unisoftplus),
MathExpParses (c-sharpcorner), Math Parser (bestcode), etc. Perhaps
look into these as a means to achieving your goal.

An interesting (to me) aside: if your release schedule allows, you
could also look at the DynamicQuery code from the VS 2008 samples page
on MSDN2; this is an expression parser that can read simple LINQ-style
queries and supports a few operators - but not many. The Expression
can be compiled to a delegate, for instance for invoke by a runtime
property (via something like TypeDescriptionProvider). It doesn't
support any of the Math.Whatever() methods, but it might serve as
inspiration... Or maybe I'm trying to use LINQ to solve everything at
the moment ;-p
2. Before first access to every entity object application creates
entity class (.cs file) from table structure in database and adds
expressions as properties to this file.
Warning: compiling input at runtime (and executing it) is risky unless
you really trust the source; it is very easy to insert malicious code
into a formula. Just be incredibly careful! You could white-list
approved methods, but even then parsing the expression (to check) can
be a pain...

To get this working, and to allow *some* kind of compile-time
type-safety, you'd need to use some kind of factory model, i.e.
---core code---
public abstract class SomeEntity {
public string Name {...} /// "regular" methods
public int Whatever {...}
static SomeEntity Create() {
... compiles if necessary, and returns a new SomeEntityImp()
}
}
---compile template---
public class SomeEntityImp : SomeEntity {
// ... additional properties (formulae) get inserted here
}
Is this best solution ?
I don't think either is very ideal; and either could be abused... all
it needs is somebody to create a recursive expression (or do something
super-super-exponential), and *boom*. This is quite
 
M

Marc Gravell

First thing is that there are two forms of
TypeDescriptor.GetProperties; type-based and instance-based. If you
are sure that individual *instances* aren't going to start managing
their properties individually, then you can use the type-based form,
so
TypeDescriptor.GetProperties(obj.GetType())[propertyName]
or
TypeDescriptor.GetProperties(typeof(YourEntity))[propertyName]
would work

However, if your GetHashCode() throws an exception for an object that
is in use like this, then your GetHashCode() is wrong.
public override int GetHashCode() {
int hash = 57;
hash = 27 * hash * id.GetHashCode();
return hash;
}

First, I'd gracefull handle the case where id is null.
Second, unless this is just illustrative, note that the 57 and 27 do
nothing.

public override int GetHashCode() {
return id == null ? 17 : id.GetHashCode(); // why not...
}

Note also that it is important to keep GetHashCode() and Equals() on
speaking terms with eachother. You probably have done, but just
thought I'd mention it... If a.Equals(b), then a.GetHashCode() *must*
equal b.GetHashCode(); however, it is not 2-way; it is perfectly fine
for the hash-codes to match and yet for a.Equals(b) to return false.

Marc
 
A

Andrus

Marc,
To get this working, and to allow *some* kind of compile-time type-safety,
you'd need to use some kind of factory model, i.e.
---core code---
public abstract class SomeEntity {
public string Name {...} /// "regular" methods
public int Whatever {...}
static SomeEntity Create() {
... compiles if necessary, and returns a new SomeEntityImp()
}
}
---compile template---
public class SomeEntityImp : SomeEntity {
// ... additional properties (formulae) get inserted here
}

I'm planning to create "standard" entity assembly containing core properties
which are used from application code in stongly typed way.
This assembly is referenced from VS2005 projects.

At application start but before accesing entity classes, application
compiles new assembly containing additional properties which customers may
have added to server tables.

This eliminates need of factory pattern.

Andrus.
 
M

Marc Gravell

At application start but before accesing entity classes, application
compiles new assembly containing additional properties which
customers may have added to server tables.

(assuming we're still talking about formulae properties)
Fair enough, but you still need to get hold of the new types and
create instances of them. When I have done similar, I have found that
the best approach is actually a generics-trick (see below), since you
then don't need any reflection (except for the initial startup). The
only other options involve some kind of factory, or getting hold of
the ConstructorInfo instances everywhere.

But again, I strongly warn against taking user-entered formulae and
compiling into code! To me, this would rank right up there with
"&admin=false" (as a web-page query-string), or SQL-injection
vulnerability.

If you are only referring to regular value properties (a user-defined
DateTime called "DateOfBirth", etc), then you can do this without all
the messing. Putting together a runtime-extensible (property-bag)
class isn't actually all that hard. I have posted examples to this
forum several times. TypeDescriptionProvider is probably the way to go
in such a case, but it can't handle text formulae unless you bolt a
parser onto the end.

Generics trick to compile with typed code, but execute for a dynamic
(sub)type:

using System;
using System.Reflection;
static class Program
{
static void Main()
{
// would actually be from dynamic assembly
Assembly dynamicAssembly = Assembly.GetExecutingAssembly();
// get the user's type
Type dynamicType = dynamicAssembly.GetType("Dynamic");
// find generic SomeBodyOfCode
MethodInfo mi = typeof(Program).GetMethod(
"SomeBodyOfCode",
BindingFlags.Static | BindingFlags.NonPublic);
// get constructed closed SomeBodyOfCode
mi = mi.MakeGenericMethod(dynamicType);
// invoke
mi.Invoke(null, null);
}
private static void SomeBodyOfCode<T>()
where T : EntityBase, new() {
// can use all EntityBase properties &
// behaviors, new(), etc - but no reflection
// involved
}
}
// from core library
public abstract class EntityBase { }

// from dynamic library
public class Dynamic : EntityBase { }
 
A

Andrus

(assuming we're still talking about formulae properties)
Fair enough, but you still need to get hold of the new types and create
instances of them. When I have done similar, I have found that the best
approach is actually a generics-trick (see below), since you then don't
need any reflection (except for the initial startup). The only other
options involve some kind of factory, or getting hold of the
ConstructorInfo instances everywhere.

I need extensible entity classes.
I do'nt understand how your trick can be used to extend Customer
class with new properties. I tried code below but this causes NRE.

So I'm planning to use the following:

1. For application design, create unsigned entity assembly containing common
methods.

public class Customer : EntityBase<Customer> {
string id;

public string Id {
get {
return id;
}
set {
id = value;
}
}
}

This assembly is referenced from projects and allows to use all standard
properties and methods.

2. At application startup, create fully Customer class assembly from table
structure containing
(if user adds) additional properties like

public class Customer: EntityBase<Customer> {

string id;

public string Id {
get {
return id;
}
set {
id = value;
}
}

string name;
public string Name {

get {
return name;
}

set {
name = value;
}
}

public string FullName {
get {
return "Mr " + Name;
}
}
}

and replace dll file in disk with this new file.

3. Now .NET if foolished and it loads the new assembly at runtime expecting
that this is original assembly shipped with application.

This does not require to use any reflection calls.
But again, I strongly warn against taking user-entered formulae and
compiling into code! To me, this would rank right up there with
"&admin=false" (as a web-page query-string), or SQL-injection
vulnerability.

I'm creating client-server desktop application which runs in client side.
Client can crack this application in any way. Yes, it can corrupt his/her
local computer and corrupt his data in server.
However, client has acces to his computer and to his data anyway and can
corrupt them in any other ways also.
SQL Server asks from user/name password and allows to acces only authorized
data at protocol level. There is no way to use this application for
unauthorized access to restriced resources in server. So I do'nt see ANY
vulnerabilites here. I recommend user to backup data before changing
formulas.
If you are only referring to regular value properties (a user-defined
DateTime called "DateOfBirth", etc), then you can do this without all the
messing. Putting together a runtime-extensible (property-bag) class isn't
actually all that hard. I have posted examples to this forum several
times. TypeDescriptionProvider is probably the way to go in such a case,
but it can't handle text formulae unless you bolt a parser onto the end.

How to create TypeDescriptionProvider which compiles poperty expression to
static method and executes it in the fly?
This would be good solution which allows creating dynamic assemblies only
for extensible properites.


Code causing NRE:

using System;
using System;
using System.Reflection;
static class Program {

static void Main() {

// would actually be from dynamic assembly
Assembly dynamicAssembly = Assembly.GetExecutingAssembly();

// get the user's type
Type dynamicType = dynamicAssembly.GetType("CustomerExtension");

// find generic SomeBodyOfCode
MethodInfo mi = typeof(Program).GetMethod(
"EntityBase",
BindingFlags.Static | BindingFlags.NonPublic);

// get constructed closed SomeBodyOfCode
mi = mi.MakeGenericMethod(dynamicType);
// invoke
mi.Invoke(null, null);
// Need to create customer object containing properties
// and methods defined in runtime.
Customer Customer = new Customer();
// set dynamic property, called from DataGridView PushCellValue
Customer.SetValue("Name", "Marc");
}
}

// from core library
public class Customer : EntityBase<Customer> {

string id;
public string Id {

get {
return id;
}

set {
id = value;
}
}
}

// from core library
public abstract class EntityBase<T> {

public virtual void SetValue(string propertyName, string propertyValue) {
PropertyInfo p = GetType().GetProperty(propertyName);
p.SetValue(this, propertyValue, null);
}
}

// from dynamic library
public class CustomerExtension : Customer {
string name;
public string Name {

get {
return name;
}

set {
name = value;
}
}

public string FullName {

get {
return "Mr " + Name;
}
}
}
 
M

Marc Gravell

NRE:
You are using the method-lookup from my code ("SomeBodyOfCode"), but
with your class name ("EntityBase") - i.e. you have mis-translated the
code I posted. Instead of using the generics trick (as suggested) you
are sticking with reflection. Fine, it'll work, but it is much harder.
Just use:
Customer cust = (Customer) Activator.CreateInstance(dynamicType) and
it should work - cust will be an instance of CustomerExtension, which
inherits from Customer so the cast will work - *assuming* that you got
the references correct when compiling the dynamic assembly. This
*must* be the same Customer type - it is not sufficient to copy the
Customer etc code into the dynamic assembly; this will be a different
Customer class and the cast will fail.

Which all brings me back to my main point: dynamically building code
from source like this is messy! Especially with reflection thrown
in...
How to create TypeDescriptionProvider which compiles poperty
expression to static method and executes it in the fly?
This would be good solution which allows creating dynamic assemblies
only for extensible properites.
Personally, that isn't the way I would approach it. I would use a
parser (like on of those already cited) to find something that I can
invoke - but that doesn't necessarily mean compiling. I would use
TypeDescriptionProvider to add a runtime-only PropertyDescriptor into
the mix, and in the GetValue etc I would invoke the above expression.
No need for compiling; no need for dynamic types - just the types you
already defined in your object model. Just smart use of the
"System.ComponentModel" area. And likewise for additional read/write
data properties; put a property-bag data-store[*1] into the EntityBase
object model (Dictionary<string,object> would do), and somewhere
(prefereably static) to keep the track of the extra properties[*2],
and just add in PropertyDescriptors (from the list in [*2]) whose
GetValue and SetValue talk the the instance's property-bag (in [*1]).

The above is a little more complex than some code, but I would expect
(from experience) it to be a lot more robust than messing with
compiling code (especially user-code) and hooking it all together with
reflection.

Marc
 
A

Andrus

Marc,
You are using the method-lookup from my code ("SomeBodyOfCode"), but with
your class name ("EntityBase") - i.e. you have mis-translated the code I
posted. Instead of using the generics trick (as suggested) you are
sticking with reflection. Fine, it'll work, but it is much harder. Just
use:
Customer cust = (Customer) Activator.CreateInstance(dynamicType) and it
should work - cust will be an instance of CustomerExtension, which
inherits from Customer so the cast will work - *assuming* that you got the
references correct when compiling the dynamic assembly.

I created two versions of dynamic customer definition which can be tested in
code below by changing #if true to #if false.

Results are the same, both require to use
reflection one time but CreateInstance requires less code and does not
require factory class.
Why your generic trick is better ?
Why you do'nt use MakeGenericType instead of MakeGenericMethod ?

Code which I posted in previous message using dynamically compiled assembly
which replaces customer assembly at runtime. This allows to use Customer
type with dynamic properies without instantiating object. How to use
extended customer type directly in this case?

Andrus.

using System;
using System.Reflection;
using System.Windows.Forms;

static class Program {

static void Main() {

// would actually be from dynamic assembly
Assembly dynamicAssembly = Assembly.GetExecutingAssembly();

// get the user's type
Type dynamicType = dynamicAssembly.GetType("CustomerExtension");

#if true
// use shorter way
CustomerBase DynamicCustomer =
(CustomerBase)Activator.CreateInstance(dynamicType);
#else
// Use Marc generic trick
MethodInfo mi = typeof(BusinessObjectFactory).GetMethod(
"CustomerFactory",
BindingFlags.Static | BindingFlags.Public);

// get constructed closed SomeBodyOfCode
mi = mi.MakeGenericMethod(dynamicType);
// invoke
CustomerBase DynamicCustomer = (CustomerBase)mi.Invoke(null, null);
#endif

DynamicCustomer.SetValue("Name", "Marc");
MessageBox.Show(DynamicCustomer.GetStringValue("FullName"));
}
}

// Required only for Mark Generic trick
public class BusinessObjectFactory {

public static CustomerBase CustomerFactory<T>()
where T : CustomerBase, new() {

// can use all EntityBase properties &
// behaviors, new(), etc - but no reflection
// involved
return new T();
}
}

// from core library

public class CustomerBase : EntityBase<CustomerBase> {

string id;
public string Id {
get {
return id;
}
set {
id = value;
}
}
}

// from core library
public abstract class EntityBase<T> {

public virtual void SetValue(string propertyName, string propertyValue) {
PropertyInfo p = GetType().GetProperty(propertyName);
p.SetValue(this, propertyValue, null);
}

public virtual string GetStringValue(string propertyName) {
PropertyInfo p = GetType().GetProperty(propertyName);
return p.GetValue(this, null).ToString();
}
}

// from dynamic library
public class CustomerExtension : CustomerBase {

string name;
public string Name {
get {
return name;
}

set {
name = value;
}
}

public string FullName {
get {
return "Mr " + Name;
}
}
}
 
M

Marc Gravell

Why you do'nt use MakeGenericType instead of MakeGenericMethod ?
The reason I used GetGenericMethod() [not GetGenericType()] was
because I was trying to remove the need for *any* of the downstream
code to have to know about the specific type involved, which was
achieved by the new() constraint. Passing the Type around and using
CreateInstance with casting will work too.
which replaces customer assembly at runtime.
not entirely convinced of this... since you can't even properly unload
an assembly, and your code must be referencing the dll to talk to the
(compile-time) Customer class... are you strong-naming?
It seems like you're trying to pull the wool over the runtime's
eyes... you might get away with it, but I wouldn't recommend it... but
if it works...
This allows to use Customer type with dynamic properies without instantiating object.
How to use extended customer type directly in this case?
I'm sorry, I don't understand the question. However, the generic
method (executing with T as the extended customer type) *is* using the
extended customer type directly. I don't know if this was the
question.
// Required only for Mark Generic trick
I had no need of a factory class (as shown in my example), so don't
blame me for this inclusion... this is yours, not mine. I simply
switched into an ineer method (quite normal when refactoring), run
running that generic method with a dynamic T.

Marc
 
M

Marc Gravell

(test; I posted a reply, but it isn't showing yet... will this? gotta
love nntp...)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top