I have to build a special class behavior depending of the constructor's argument. Say the Foo drawing should became forever green, if it was build(drawn) with a green pencil. If any pencil where used, the Foo should be transparent...
Now, look at the code bellow. Is there a possibility to modify the "output" that the constructor "see" the real type of the passed in parameter object? (actually they are all "object"s):
class Program
{
static void Main(string[] args)
{
object[] objs = { new IndexOutOfRangeException(), MyEnum.Beta, 45, new AssemblyName(), new { Name = "a" } };
for (开发者_高级运维int i = 0; i < objs.Length; i++)
{
Console.WriteLine("{0} => {1} ", i, objs[i]);
}
Console.WriteLine("=========================== ");
for (int i = 0; i < objs.Length; i++)
{
Foo myFoo = new Foo(objs[i]);
Console.WriteLine("{0} => {1}", i, myFoo);
}
}
}
public class Foo
{
object value;
string typeName;
public Foo(object obj)
{
value = obj;
typeName = "object";
}
public Foo(MyEnum enm)
{
value = enm;
typeName = "MyEnum";
}
public Foo(int myInt)
{
value = myInt;
typeName = "int";
}
public Foo(Exception ex)
{
value = ex;
typeName = "exception";
}
public override string ToString()
{
return string.Format("FOO (object = '{0}'; type = '{1}')", value, typeName);
}
}
public enum MyEnum
{
Alpha,
Beta
}
OUTPUT
0 => System.IndexOutOfRangeException: Index was outside the bounds of the array.
1 => Beta
2 => 45
3 =>
4 => { Name = a }
===========================
0 => FOO (object = 'System.IndexOutOfRangeException: Index was outside the bound
s of the array.'; type = 'object')
1 => FOO (object = 'Beta'; type = 'object')
2 => FOO (object = '45'; type = 'object')
3 => FOO (object = ''; type = 'object')
4 => FOO (object = '{ Name = a }'; type = 'object')
EDIT:
As see some answers, I would like to stress that is not about the correct string to be displayed in the "type" variable, like using value.GetType()
, but is about "entering" in the correct constructor is the question.
In other words, Why does the compiler not detect the correct type and "redirects" it to the correct constructor?
EDIT 2:
As mentioned by the answerers, the "way" to constructor is "built" at compile time, not in runtime. say a code like his
MyEnum en = MyEnum.Beta;
Console.WriteLine("Enum example: obj:{0} Foo:{1}", en, new Foo(en));
Will output the "good" output:
Enum example: obj:Beta Foo:FOO (object = 'Beta'; type = 'MyEnum')
so... apparently, any way to "bypass" this behavior but the runtime detection in constructor, like proposed Reed Copsey... ?!
Why does the compiler not detect the correct type and "redirects" it to the correct constructor?
This is because you're passing in the object as a System.Object
. (object[] objs = { n...
) The constructor is chosen at compile time, not at runtime. The declaration of the variable used is seen by the compiler, and used to check for the appropriate type.
As you mentioned in another comment:
ok, what if I have a big array of objects, and don't know, a priori their type?
This is exactly why the compiler works this way. It can't know, at compile time, which constructor you want, and since you have a System.Object
constructor which works, it gets chosen.
If you want to handle these specific types separately, but still construct the object as a System.Object
, you'd have to add checks for that inside of the constructor for object and handle specific cases separately. This is not the most maintainable code, however, if you do that.
public Foo(object obj)
{
value = obj;
typeName = "object";
// Change typeName if appropriate
if (obj != null)
{
if (obj is MyEnum)
typeName = "MyEnum";
else if (obj is int)
typeName = "int";
else if (obj is Exception)
typeName = "exception";
}
}
Edit:
Given that, in your real code, the constructor is likely going to do a lot more work, I would consider making a factory method to handle this. It would allow you to use a similar approach as above, but leave the type safe constructors in place:
// I'd make the object constructor private, to prevent accidental usage:
private Foo(object obj) { ...
public static Foo CreateAppropriateFoo(object obj)
{
if (obj == null)
return new Foo(obj); // Use object constructor
else
{
if (obj is MyEnum)
return new Foo( (MyEnum)obj );
else if (obj is int)
return new Foo( (int)obj );
else if (obj is Exception)
return new Foo( (Exception)obj );
}
}
This, at least, prevents the duplication of the constructor logic, as well as makes it a little more obvious that there is some logic happening at runtime.
I think you can get what you wanted by either remove the ctor which accepts an object parameter (then everything should bind to the proper constructor properly), or by casting a the parameter to proper type, for example this one:
var myFoo = new Foo(objs[0] as Exception);
will most likely use the ctor with an Exception parameter.
EDIT: your question: Why does the compiler not detect the correct type and "redirects" it to the correct constructor?
my answer: because you are passing a member of an object array so the compiler is right in handling that actual object instance as object.
it's like the catch blocks which must be ordered from most generic to more general going down, you have a generic catcher at the top having your ctor to accept an object and you should not be surprised that passing a value from an object array the compiler uses the most suitable candidate. That's why I suggested you to do a cast on calling the ctor, so that you can esplicitely tell that you want to cast objs[0] to an Exception object, or the compiler will consider it as object.
精彩评论