Reading method values at runtime using reflection (+hack)? - c#

Brief:
We have a C# method which accept parameters.
inside of that method we need to add Sql parameters (according to the method parameters).
It actually looks like this :
http://i.stack.imgur.com/QP1y2.jpg (to enlarge)
Please notice
I can write a class which will have all the method arguments. But then , I will have to create a class container for each SP ( and we have a lot).
Ok lets continue.
I was thinking to myself : "WHY should I insert each param manually - If I already have its : name , type , value in the method param ? I don't want to do this , maybe reflection will do this for me" ( lets leave for now the conversion for int <->DbType.Int32)
But I encountered a problem. it is not possible reading values using reflection.
However , I did managed to read values via reflection with a hack.
1) I did this example: ( simulation to the real scenario)
public static void MyMethod(int AddressId, string countryId, DateTime dt) //these are the arguments which need to be attached later
{
MethodBase mi = MethodInfo.GetCurrentMethod();
var hack = new {AddressId, countryId, dt}; //this is the HACK
var lstArguments = mi.GetParameters() //get the parameter name and types
.Select(p => new {
name = p.Name , type=p.GetType().Name
}).ToList();
List < dynamic > lstParamValues= new List < dynamic > ();
foreach(PropertyInfo pi in hack.GetType().GetProperties()) //get the value(!!!) of each param
{
lstParamValues.Add(pi.GetValue(hack, null));
}
dynamic dyn= myDBInstance; // get dynamic reference to the DB class instance (
for(int i = 0; i < lstArguments .Count; i++) //dynamically Invoke the method with param name+value.
{
dyn.AddInParameter(lstArguments [i].name, ((lstParamValues[i] is int) ? DbType.Int32 : DbType.String), lstParamValues[i]);
}
}
And there is the DB class ( to simulate the AddInParameter to see if im getting values)
class myDB
{
public void AddInParameter(string name, DbType dbt, object val)
{
Console.WriteLine("name=" + name + " " + "DbType=" + dbt + " " + "object val=" + val);
}
}
And it is working :
I executed the method with :
MyMethod(1, "2", DateTime.Now);
And the output was :
name=AddressId DbType=Int32 object val=1
name=countryId DbType=String object val=2
name=dt DbType=String object val=05/02/2013 19:09:32
All fine.
the C# Question
How can I get over this hack :
var hack = new {AddressId, countryId, dt}; // the method arguments names
In my sample I wrote it hard coded.
The reflection method GetValue is working only because of this line.
Is there anything I can do to add on run-time the method arguments name as a property , so I will be able to do :
foreach(PropertyInfo pi in SOMETHING.GetType().GetProperties())
{
}
Please consider this question as a c# question and not ORM question.
Also , If you want to play with running sample code ( just paste in console proj) : http://jsbin.com/azicut/1/edit

Does this provide some relief?
Do this once (in the DAL ctor):
SqlParameter sqlCmdFTSindexWordOnceParam = sqlCmdFTSindexWordOnce.Parameters.Add("#sID", SqlDbType.Int);
sqlCmdFTSindexWordOnceParam.Direction = ParameterDirection.Input;
SqlParameter sqlCmdFTSindexWordOnceParam2 = sqlCmdFTSindexWordOnce.Parameters.Add("#sIDpar", SqlDbType.Int);
sqlCmdFTSindexWordOnceParam2.Direction = ParameterDirection.Input;
Then in the call:
sqlCmdFTSindexWordOnceParam.Value = wdc.sID;
sqlCmdFTSindexWordOnceParam2.Value = wdc.sIDpar;
sqlCmdFTSindexWordOnce.BeginExecuteNonQuery(callbackFTSindexWordOnce, sqlCmdFTSindexWordOnce);
Better performance than adding the parameter for each call.

AddWithValue at least will save you from writing type name.
command.Parameters.AddWithValue("#demographics", demoXml);
But also you really shouln't pass that much parameter to a function. A simple DTO class can make your code much more readable. If I am not mistaken you are looking something like pyhton's locals (Need something like locals in python) which gives you local variables of function as key-value pair and you can't do this in c# as far as i know.
or you simple do not pass all variables you just can pass a Dictionary<string,object> and add key values to SqlParameter

i'm not familiar with the workings of c# and .net, but if this was java, i would say:
store the current parameters into an arraylist or map
you could either have 3 arraylists, one for each of the parameters that go into the addInParameters method call, or a map with all the values included in it (the type parameter would need to have some duplication as it does now)
use the array list as the only parameter in the insert/update methods
use a for loop so that the AddInParameter call is only written once, and the parameters are like (array1[0], array2[0], array3[0]) rather than the actual values.
in php you could use associative arrays, does .net have associative arrays?

Related

C# compiler error with explicit type vars sent to method with ref objects (converting from VB.net)

Long time programmer, new to C#. I am in the process of converting a solution from VB.net to C#. This particular function "getdata" returns values from the first row in a sql select. For this example I've simplified the code.
Due to the unknown datatypes being fetched from sql, the "getdata()" parms are objects. VB allows calling a function with any explicit datatype byref parms into objects, so I can send a string or int parm into an object and return it with no issues.
In C#, this method works for passing parms by value. Any type of byref (ref/in/out) the compiler arrors with "cannot convert from ref string to ref object"
What I've tried:
Changing all manner of parm ref/var type (in/out/ref/nothing)
Changing datatypes of variables from explicit to object works but produces lots of issues downstream. ie. I don't want to define everything as an object/I much prefer explicit datatypes.
explicit cast before calling using (object) before the variable name.
Changing everything to Dynamic types works but same issues as object.
My best solution, unfortunately, changes the functionality enough that it's going to cause issues with further solution conversion. I came up with returning the a/b/c variables as an anonymous object that are that set to the actual variables upon return to the calling function.
Is there any way that calling function parms can be explicitly typed and passed to an implicit data type like object?
If not, any better solution than returning anonymous type?
VB code -- working
Private Sub test()
Dim a$, b%, c$
getdata(1, a, b, c)
MsgBox($"a={a}, b={b}, c={c}")
Dim x As DateTime, y As String, z As String
getdata(2, x, y, z)
MsgBox($"x={x}, y={y}, z={z}")
End Sub
Private Sub getdata(opt As Integer, ByRef val0 As Object, ByRef Optional val1 As Object = Nothing, ByRef Optional val2 As Object = Nothing) As Boolean
'the real implementation of this function will accept sql string and return first row of data columns
'since fetched data will be of different types, parms are defined as objects
If opt = 1 Then
val0 = "Apples"
val1 = 2
val2 = "Oranges"
ElseIf opt = 2 Then
val0 = now
val1 = "Dogs"
val2 = "Cats"
End If
End Function
C# code -- compiler error -
I am hand converting VB code to help with the C# learning curve but my last ditch solution was to use a VB->C# converter which is produced here.
private void test()
{
string a = null;
int b = 0;
string c = null;
getdata(1, ref a, ref b, ref c); ************** error occurs here
MessageBox.Show($"a={a}, b={b}, c={c}"); "cannot convert from ref string to ref object"
DateTime x = default(DateTime);
string y = null;
string z = null;
getdata(2, ref x, ref y, ref z); ************** error occurs here
MessageBox.Show($"x={x}, y={y}, z={z}"); "cannot convert from ref string to ref object"
}
private bool getdata(int opt, ref object val0, ref object val1, ref object val2)
{
//real function will accept sql string and return first row of data columns
//since fetched data will be of different types, parms are defined as objects
if (opt == 1)
{
val0 = "Apples";
val1 = 2;
val2 = "Oranges";
}
else if (opt == 2)
{
val0 = DateTime.Now;
val1 = "Dogs";
val2 = "Cats";
}
return true;
}
There are some fundamental things in this method which make me believe you should spend more time refactoring vs direct translation. Restoring type safety is one of them (VB.Net made it easier to hide some poor type safety choices in a file where you have Option Strict Off for a couple modules), but this also REALLY scares me:
//real function will accept sql string
Functions like that tend to cause HUGE security problems, as well as other issues, especially when you also have a bunch of arguments for output values. If you're not well-versed in SQL Injection, NOW is the time to learn about it. You must also provide a way to include input data for the SQL command that is completely separate from the SQL string itself, or you'll eventually find yourself in big trouble.
This code needs some serious refactoring, not just simple conversion!
I suggest refactoring around a method like this:
public class DB
{
private static string ConnectionString {get;} = "connection string here";
private static IEnumerable<IDataRecord> getdata(string sql, Action<SqlParameterCollection> addParameters)
{
using (var cn = new SqlConnection(ConnectionString))
using (var cmd = new SqlCommand(sql, cn))
{
addParameters(cmd.Parameters);
cn.Open();
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
yield return rdr;
rdr.Close();
}
}
}
}
Notice the method is private; this is because we're not done building the class yet. Once you create this class, and remove the old getdata() method, everywhere that currently calls this method will turn into a compiler error. This is good; it gives you an easy way to find all those places you had poor code of this type.
So now we start looking at the new compiler errors. Each one will represent a place where you used to call getdata(). There's probably other code nearby to build up the SQL string. You want to move each of these sections to a new static method in the DB class.
One of those methods might look something like this:
public static IDataRecord MyNewDataMethod(int ID)
{
string SQL = "SELECT ... WHERE ID = #ID";
return getdata(SQL, p => {
p.Add("#ID", SqlDbType.Int).Value = ID;
}).FirstOrDefault();
}
But we can (and should) take this a step further. Typically, these results will represent objects of some type. After all, they had to come from a table, or at least a set of related tables. If you don't already have a class for each of these things, you probably should. These classes should have static methods named something like FromDataRecord(), which accept an IDataRecord or DataRow as input and return the class type as output. They are Factory methods. And now we update the methods to look more like this:
public static MyObjectType MyNewDataMethod(int MyObjectTypeID)
{
string SQL = "SELECT ... WHERE ID = #ID";
return getdata(SQL, p => {
p.Add("#ID", SqlDbType.Int).Value = MyObjectTypeID;
}).Select(MyObjectType.FromDataRecord).FirstOrDefault();
}
Here's another example that might return several records:
public static IEnumerable<MyObjectType> MyNewDataMethod(string SearchKey)
{
string SQL = "SELECT ... WHERE SearchColumn = #SearchKey + '%'";
return getdata(SQL, p => {
p.Add("#SearchKey", SqlDbType.NVarChar, 80).Value = SearchKey;
}).Select(MyObjectType.FromDataRecord);
}
If you find you have a lot of these methods, you can convert the private getdata() method to protected, put it in it's own class library project in the solution, and use separate public classes in the same project that can still access that method to divide the data access into logical areas.
I agree with Joel's sentiments; throw this code away rather than trying to salvage it. It's garbage.
If you add a reference to the Nuget package Dapper, your life will get a lot easier. With Dapper, you write SQL and it maps to objects for you. It looks something like this:
using(var c = new SqlConnection(connection_string_here){
var person = c.QueryFirst<(string Na, string Ad, int Ag)>("SELECT name, address, age FORM person WHERE id = #id", new { id = 123 });
}
There's a lot going on in this, so i'll unpack it:
The first line just creates a database conenction in a using, so it will be disposed of. You don't need to bother about anything else; Dapper will open the connection, use it, close it
The second line has some parts:
var person = - like Dim x = 1 in VB, var declares a variable that is type-detected by the compiler from whatever type is on the right hand side
c.QueryFirst<(string Na, string Ad, int Ag)> - QueryFirst is a Dapper extension method that runs a select query and pulls the first row. Dapper maps the query columns to the type you give in angle brackets. Here I've given a ValueTuple which is a way to get the C# compiler to "fake" a class for you based on the ValueTuple class. A discussion about how it works is a bit out of scope, but suffice to say when the compiler encounters (string X, string Y, int Z) it transforms behind the scenes into something that you can refer to as an object with those named/typed properties. Suffice to say, when all is done, you'll be able to say person.Na or person.Ad in your code
"SELECT name, address, age FORM person WHERE id = #id" - is a parameterized SQL. It looks up a person with some ID and pulls their data out in that order, name, address, age. The order in this case is important because AFIAWA dapper maps ValueTuples positionally, not by name. This is different to other things (example later) where it does map by name. The tuple has name/address/age, so the query pulls them in the same order
new { id = 123 } - is creating a C# anonymous type, a sort of internal-only compiler generated class (different to a valuetuple) that has no name, but does have a property called id with value 123. Dapper will scan your SQL string looking for parameters, and find one called #id, so it will pull the value 123 out of the supplied anonymous type's id property (name based this time, not positional)
If you have a class Person lying around, as you probably should if you're doing any reasonable amount of database-to-c#-and-back-again work, then the call can look like this:
class Person{
public string Name {get; set;}
public string Address {get; set;}
public int Age {get; set;}
}
...
c.QueryFirst<Person>("SELECT age, name, address FROM ... WHERE id = #i", new { i=123 });
This time we pass a full class Person - Dapper will map the proeprties by name, which is why they're in a different order in the SQL (it could even be SELECT * and dapper will just ignore the 10+ columns in our person table that arent represented by an class property) and it still works. If your SQL names don't match your class names, the simplest thing to do is alias them in the SQL:
c.QueryFirst<Person>("SELECT firstname+' '+lastname as name, ... FROM ... WHERE id = #i", new { i=123 });
I don't think there's an elegant solution - you can keep your 'getdata' method unchanged if you add extra baggage to every method call:
private void test()
{
string a = null;
int b = 0;
string c = null;
object temp_a = a;
object temp_b = b;
object temp_c = c;
getdata(1, ref temp_a, ref temp_b, ref temp_c);
a = (string)temp_a;
b = (int)temp_b;
c = (string)temp_c;
MessageBox.Show($"a={a}, b={b}, c={c}");
}
I ended up with the following
Hashtable gd = getData();
string location = (string)gd["location"];
int locationid = (int)gd["locationid"];
string frutata = (string)gd["frutata"];
where getData() just builds a hashtable of objects with the datareader columns.
My end goal was to create a simple callable function w/o a bunch of code to handle return values.
Dapper seems pretty cool and I will def. check that out.

C# activate class with <SomeType>, with a body, dynamically, based on a "type" variable

I was looking around here for the answer to this question, and I found a lot of similar questions
Passing just a type as a parameter in C#
X is a variable but used like a type when trying to cast
how to adjust "is a type but is used like a variable"?
How to pass an Object type to Type parameter in C#
Generic List<T> as parameter on method, Initializing a Generic variable from a C# Type Variable
How do I use reflection to call a generic method?
Reflection To Execute Class With Generic Type
but I wasn't able to use any of them to solve my particular issue.
Basically, I have a class (that I don't want to modify) that is activated in a form like so:
var myEvent = new EventListener<KeyboardEvent/*or other type of event*/>((e) => {
///do stuff with event "e"
});
I want to make a function that makes a new event listener "dynamically", meaning based on a particular variable for the "event type" (forget about the function body for now, just assume they all have the same function body), like:
void makeEvent(Type eventType) {
var myEvent = new EventListener<eventType>((e) => {
///do stuff with event "e"
});
}
as many of you will know, as did those people who posted the above questions, that simple doing that will give a "variable used like a type" error, and it won't work, and many of them suggested using "reflection" to get around this, like (from Reflection To Execute Class With Generic Type):
ar instance = Activator.CreateInstance(implementation);
var results = this.GetType()
.GetMethod("CheckForMessages", BindingFlags.NonPublic | BindingFlags.Instance)
.MakeGenericMethod(interfaceUsesType)
.Invoke(this, null) as object[];
if(results.Count() > 0)
instance.GetType()
.GetMethod("DoThis")
.Invoke(instance, new object[] {results});
or (from Initializing a Generic variable from a C# Type Variable):
Animal a = MyFavoriteAnimal();
var contextType = typeof(EsbRepository<>).MakeGenericType(a.GetType());
dynamic context = Activator.CreateInstance(contextType);
context.DoAnimalStuff();
So these answers theoretically make a class definition, but in my case I need to do 2 things, #1: make the actual class of the EventListener, and #2 actually give the class a body (via the lambda expression above), so how can I do that with Activator.CreateInstance ? Or Is there any other way?
Basically I can't use a lambda in the object[], because its not an object, and if I use an Action of some kind that is an object, then I would need to pass in a generic type, and I'm back to where I started, for example I could theoretically do:
var myType = typeof(EventListener<>).MakeGenericType(eventType);
Activator.CreateInstance(myType, new object[] {
new Action<KeyboardEvent>(e => {})
});
which would compile, but then I'm back to where I started in the first place, because "KeyboardEvent" is itself what I need to change, and if I do:
Action<myDynamicTypeVariable>(e=>{})
I get the same "variable is used as type" error...
Isn't there just some kind of way to actually use a variable as a type?
Or is there a way to set the body of a function after the class instance has been formed?
Or how can I pass in a generic function as one of the object[] arguments without having to specify the type of the function and without using lambdas?

generate predictable unique string based on a generic objects content

Story
I'm trying to write a generic method which combines property names, types and content value to generate a unique string for the value held by the object passed.
The idea is to generate a unique SHA3-512 Hash based on the generated string sequence which can be used to compare objects on generic bases by looking at their content.
Example
Let's say we have a class like this ...
class MyClass {
private Int32 Id = 5;
public String Name = "some string";
protected DateTime CreateDate = DateTime.Parse("2017-08-21 15:00:07");
}
... and the mentioned method to generate the unique string
static String GetContentString<T>(T obj) where T : class {
...
}
In theory this should work somewhat like this:
var myObj = new MyClass();
var uniqueContentString = GetContentString(myObj);
Console.WriteLine(uniqueContentString);
>>> Id:Int32:5$Name:String:some string$CreateDate:DateTime:2017-08-21 15:00:07
Problem
I'm having difficulties building the GetContentString Method. This is what I have already:
Object obj = ... // given object
Type type = obj.GetType();
IList<PropertyInfo> propertyInfos = type.GetProperties().Where(x => x.CanRead).ToList(); // Marker #2
StringBuilder sb = new StringBuilder();
foreach (PropertyInfo pi in propertyInfos)
{
sb.Append(pi.Name);
sb.Append(":");
sb.Append(pi.PropertyType.Name);
sb.Append(":");
sb.Append(pi.GetValue(obj) ?? "[ISNULL]"); // Marker #1
sb.Append(":");
}
return sb.ToString();
I tried running the method for a few different types of values like "some string" or 1234 (Int32) and ran into a few issues.
Given a string, the method call throws an exception of type System.Reflection.TargetParameterCountException and the message Parameter count mismatch at #1. I found out that an optional index can be passed to an overloaded version of pi.GetValue(..) which then returns one of the single letters. But how do you know when to stop? If you call an index which doesn't exist it throwns an exception of the type System.Reflection.TargetInvocationException. How do you get the value of a string object using reflection?
Given an integer value, the method call doesn't find any properties at #2. Which brings up the question of how to get the value of an integer object using reflection?
And also some general questions; do you guys think this is a good approach to get a unique string? Is reflection the way to go here? Is it even possible to write a generic solution to this problem?
Without looking at reflection, how about JSON serialization with something that .net framework is able to ?
Reflection isn't something extremely fast and you'll run into issues at the first unhandled exception.
Then, you should do that recursivly if your objects can contains complex properties, wich is not a problem with json serialization !

Passing IEnumerable data from LINQ as parameter to a method [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How can I pass an anonymous type to a method?
I have the following LINQ Statement, whose output has to be processed in another method:
var data = from lines in File.ReadAllLines(TrainingDataFile)
.Skip(ContainsHeader ? 1 : 0)
let f = lines.Split(new[] { FieldSeparator }).ToList<String>()
let target = f[TargetVariablePositionZeroBased]
select new { F=f, T=target };
What should be the datatype of the parameter in the method that will take this data?
You can not return the anonymous data types from a method. You can define a class and return object of that class from query and pass it to target method.
public class SomeClass
{
public string F {get; set;}
public string T {get; set;}
}
var data = from lines in File.ReadAllLines(TrainingDataFile)
.Skip(ContainsHeader ? 1 : 0)
let f = lines.Split(new[] { FieldSeparator }).ToList<String>()
let target = f[TargetVariablePositionZeroBased]
select new SomeClass { F=f, T=target };
You can pass the query result IEnumerable<SomeClass> to method as parameter.
public void MethodToCall(IEnumerable<SomeClass> someClass)
{
}
To call the method by passing the query result (IEnumerable<SomeClass>) that is stored in data in this sample code
MethodToCall(data);
You can't very easily pass anonymous types around. You can either create a class, or since your data has only two properties, use a Tuple:
select new Tuple<List<string>, string> (f, target);
If I have the data types correct, then the data type of the parameter would be:
IEnumerable<Tuple<List<string>, string>>
and you would reference F and T using the Tuple properties Item1 and Item2.
1) Just to pass the result of the query, make your function generic, that will do:
var data = from lines in File.ReadAllLines(TrainingDataFile)
.Skip(ContainsHeader ? 1 : 0)
let f = lines.Split(new[] { FieldSeparator }).ToList<String>()
let target = f[TargetVariablePositionZeroBased]
select new { F=f, T=target };
SomeMethod(data);
public void SomeMethod<T>(IEnumerable<T> enumerable)
{
// ^^choose the return type..
}
Simple. If the processing inside the method is something so simple this will do. But you won't be able to access properties F and T inside the method.
To do so:
2) You can use the "cast by example" trick shown here by Eric. To quote him:
We use method type inference and local variable type inference to tell
the compiler "these two things are the same type". This lets you
export an anonymous type as object and cast it back to anonymous type.
...the trick only works if the example and the source objects were
created in code in the same assembly; two "identical" anonymous types
in two different assemblies do not unify to be the same type.
SomeMethod(data);
public void SomeMethod(IEnumerable<object> enumerable)
{
var template = new { F = new List<string>(), T = string.Empty };
foreach (var item in enumerable)
{
var anonymousType = item.CastToTypeOf(template);
//print string.Join(", ", anonymousType.F) + " - " + anonymousType.T //compiles
//or whatever
}
}
//a more generic name perhaps is 'CastToTypeOf' as an extension method
public static T CastToTypeOf<T>(this object source, T example) where T : class
{
return (T)source;
}
The catch here is that SomeMethod now is tailor made for your anonymous type, since you're specifying a specific type inside the method, so its better to not make the function generic (though you can do) and to give a suitable name for the function.
3) If function is just for your unique type now, I would better have them all wrapped in a single method and not pass at all - no hassle! :)
4) Or you can delegate the action to be done on your anonymous type. So method signature would be like:
SomeMethod(data, d => print string.Join(", ", d.F) + " - " + d.T);
public void SomeMethod<T>(IEnumerable<T> enumerable, Action<T> actor)
{
foreach (var item in enumerable)
actor(item);
}
If it matters you can have Func delegate as well by having one more type argument.
5) Rely on fiddly reflection to get the properties from your anonymous type otherwise.
6) Use dynamic keyword on method argument and now you have dynamic typing. Both the above doesnt give you benefits of static typing.
7) You will be better off having a separate class that holds F and T. And that the best of all. But ask yourself do they together represent something as an entity?
8) If not, just pass an IEnumerable<Tuple> or IDictionary depending on what matters.
It all depends on what/how you want to achieve with the method. Personally, I would go for the approach 2 in a hobby project (for the fun involved), but in production code 3, 4, 7, 8 depending on the context.

Property / field initializers in code generation

I'm generating code in a visual studio extension using CodeDom and plain code strings. My extension reads a current classes declared fields and properties using reflection and generates contructors, initializers, implements certain interfaces, etc.
The generator class is simple:
public class CodeGenerator < T >
{
public string GetCode ()
{
string code = "";
T type = typeof(T);
List < PropertyInfo > properties = t.GetProperties();
foreach (PropertyInfo property in properties)
code += "this." + property.Name + " = default(" + property.PropertyType.Name + ")";
}
}
I'm stuck at field and property initializers in two ways.
Firstly, although default(AnyNonGenericValueOrReferenceType) seems to work in most cases, I'm uncomfortable with using it in generated code.
Secondly, it does not work for generic types since I can't find a way to get the underlying type of the generic type. So if a property is List < int >, property.PropertyType.Name returns List`1. There are two problems here. First, I need to get the proper name for the generic type without using string manipulation. Second, I need to access the underlying type. The full property type name returns something like:
System.Collections.Generic.List`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
Before I try to answer, I feel compelled to point out that what you're doing seems redundant. Assuming that you are putting this code into a constructor, generating something like:
public class Foo
{
private int a;
private bool b;
private SomeType c;
public Foo()
{
this.a = default(int);
this.b = default(bool);
this.c = default(SomeType);
}
}
is unnecessary. That already happens automatically when a class is constructed. (In fact, some quick testing shows that these assignments aren't even optimized away if they're done explicitly in the constructor, though I suppose the JITter could take care of that.)
Second, the default keyword was designed in large part to do exactly what you're doing: to provide a way to assign the "default" value to a variable whose type is unknown at compile time. It was introduced for use by generic code, I assume, but auto-generated code is certainly correct in using it as well.
Keep in mind that the default value of a reference type is null, so
this.list = default(List<int>);
does not construct a new List<int>, it just sets this.list to null. What I suspect you want to do, instead, is to use the Type.IsValueType property to leave value types at their default values, and initialize reference types using new.
Lastly, I think what you're looking for here is the IsGenericType property of the Type class and the corresponding GetGenericArguments() method:
foreach (PropertyInfo property in properties)
{
if (property.Type.IsGenericType)
{
var subtypes = property.Type.GetGenericArguments();
// construct full type name from type and subtypes.
}
else
{
code += "this." + property.Name + " = default(" + property.PropertyType.Name + ")";
}
}
EDIT:
As far as constructing something useful for a reference type, a common technique I've seen used by generated code is to require a parameterless constructor for any class that you expect to use. It's easy enough to see if a class has a parameterless constructor, by calling Type.GetConstructor(), passing in an empty Type[] (e.g. Type.EmptyTypes), and see if it returns a ConstructorInfo or null. Once that has been established, simply replacing default(typename) with new typename() should achieve what you need.
More generally you can supply any array of types to that method to see if there's a matching constructor, or call GetConstructors() to get them all. Things to look out for here are the IsPublic, IsStatic, and IsGenericMethod fields of the ConstructorInfo, to find one you can actually call from wherever this code is being generated.
The problem you are trying to solve, though, is going to become arbitrarily complex unless you can place some constraints on it. One option would be to find an arbitrary constructor and build a call that looks like this:
var line = "this." + fieldName + " = new(";
foreach ( var param in constructor.GetParameters() )
{
line += "default(" + param.ParameterType.Name + "),";
}
line = line.TrimEnd(',') + ");"
(Note this is for illustrative purposes only, I'd probably use CodeDOM here, or at least a StringBuilder :)
But of course, now you have the problem of determining the appropriate type name for each parameter, which themselves could be generics. And the reference type parameters would all be initialized to null. And there's no way of knowing which of the arbitrarily many constructors you can pick from actually produces a usable object (some of them may do bad things, like assume you're going to set properties or call methods immediately after you construct an instance.)
How you go about solving those issues is not a technical one: you can recursively apply this same logic to each parameter as far down as you're willing to go. It's a matter of deciding, for your use case, how complex you need to be and what kind of limits you're willing to place on the users.
If you are sure you want to use strings, you will have to write your own method to format those type names. Something like:
static string FormatType(Type t)
{
string result = t.Name;
if (t.IsGenericType)
{
result = string.Format("{0}<{1}>",
result.Split('`')[0],
string.Join(",", t.GetGenericArguments().Select(FormatType)));
}
return result;
}
This code assumes you have all necessary usings in your file.
But I think it's much better to actually use CodeDOM's object model. This way, you don't have to worry about usings, formatting types or typos:
var statement =
new CodeAssignStatement(
new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), property.Name),
new CodeDefaultValueExpression(new CodeTypeReference(property.PropertyType)));
And if you really don't want to use default(T), you can find out whether the type is a reference or value type. If it's a reference type, use null. If it's value type, the default constructor has to exist, and so you can call that.

Categories