Some sample code to illustrate what I'm talking about. I've got a utility class with a single method. That method takes one argument: a System.IO.FileInfo object. From the PowerShell side I can pass in a System.String object and everything "just works". I'm curious as a develop getting more into PowerShell this year I'm curious:
What feature of PowerShell allows this to happen?
Is this open to extension / use by any developer in PowerShell 7.x? (non-internal)
C#:
using System.IO;
using System.Linq;
namespace TestProject
{
public class Utility
{
public string GetFirstLine(FileInfo fileInfo)
{
string firstLine = File.ReadLines(fileInfo.FullName).First();
return firstLine;
}
}
}
PowerShell:
Add-Type -Path "C:\assemblies\TestProject.dll"
$util = [TestProject.Utility]::New()
$util.GetFirstLine("C:\temp\random-log.txt")
Note: I realize this is trivial example. Code is meant for quickly illustrating the capability in PowerShell I am interested in.
Although PowerShell is more or less a "real .NET language", it does extend the common type system with an array of behaviors that sometimes conflict with what you know about .NET's type system behavior from languages like C# or VB.NET.
This additional type system layer bolted on top of the CLR is aptly named the Extended Type System (ETS), and it's directly responsible for making argument conversion "just work" the way you've observed.
When the PowerShell runtime reaches a method invocation statement like $util.GetFirstLine("C:\temp\random-log.txt"), it has to pick an appropriate overload, just like the C# compiler does.
The first step is to identity overloads for which the number of arguments passed can cover all mandatory parameters of the given overload signature. In your case, only 1 such signature can be resolved at this step, so PowerShell now has two pieces of the puzzle:
A method stub with the signature string GetFirstLine(FileInfo fileInfo)
An argument value of type [string]
At this point, the C# compiler would give up and report CS1503: Argument 1: cannot convert from 'string' to 'System.IO.FileInfo'.
PowerShell, on the other hand, is designed to be helpful in the hands of system administrators and operators - people who might not put too much thought into data structure design, but who are chiefly concerned with string representations of data (after all, this is what bash, cmd, etc. taught them to use).
To meaningfully convert from a string (or any other non-compliant argument type) at just the right time, ETS comes with a number of facilities and hooks for implicit type conversion that the runtime then attempts, one by one, until it either finds a valid conversion mechanism, or fails (at which point the method invocation fails too):
Built-in converters
These take priority to handle the most common edge cases, like null-conversions, "truthy/falsy"-to-real-[bool] conversions, stringification of scalars, etc. - without these, PowerShell would be a pain in the ass to work with interactively.
Example:
These flow control statements behave exactly like you'd expect thanks to built-in conversions: if($null){ <# unreachable #> }, do{ Stop-Process chrome }while(Get-Process chome)
Custom type converters
These are concrete type converter implementations registered to one or more target types - this can be useful for modifying the binding behavior of complex types you've brought with you into the runtime.
Example:
The RSAT ActiveDirectory module uses type adapters to interchange between the different data types modeling specific directory object classes - allowing you to seamlessly pipe output from Get-ADComputer (very specific output type) to Get-ADObject (generalized output type) and vice versa.
Parse converters
If no appropriate built-in or custom type converter can be found, PowerShell will attempt to resolve a Parse() method with the appropriate return type on the target type.
Example:
The cast operation [timespan]'1.02:15:25' can succeed this way.
Constructor-based conversions
If none of the above works, PowerShell will attempt to resolve a constructor that can be invoked with a single parameter argument of the source type given.
This is what happens in your case - PowerShell effectively excutes $util.GetFirstLine([System.IO.FileInfo]::new("C:\temp\random-log.txt")) for you
CTS conversions
Finally, if all of ETS' conversation attempts fail, it falls back to resolving implicit (and eventually explicit) conversions defined by the types themselves.
Example:
This conversion succeeds because the [DateTimeOffset] type has an implicit conversion operator for [DateTime]: (Get-Date) -as [DateTimeOffset]
The concrete type converters mentioned above will automatically be respected by ETS if they've either been included in a type data file (see about_Types.ps1xml), or if the target type is public and the source definition was decorated with a [TypeConverter()] attribute.
Additionally, you can register new type conversion primitives at runtime with the Update-TypeData cmdlet.
Of course the madness doesn't stop there, there are also additional facilities specifically for converting/transform command arguments, but that's beyond the scope of this question :)
Related
Today I was looking at some code of Nancy and since I like to explore the code I noticed following thing.
While navigating in VS2017 I went to NancyModule I saw the code from the metadata the following:
public virtual void Delete(string path, [Dynamic(new[] { false, true, false })] Func action, Func condition = null, string name = null);`
Now I don't know what this attribute does so I immediately went to msdn.
Parameters
transformFlags:
Type: System.Boolean[]
Specifies, in a prefix traversal of a type's construction, which Object occurrences are meant to be treated as a dynamically dispatched type.
Remarks
For instance, if C is a generic type with two type parameters, a use of the constructed type C might be intended to treat the first type argument dynamically and the second typically, in which case the appropriate attribute specification should use a transformFlags value of { false, true, false }.
After that I realized that in Nancy's code there is no such attribute applied so I thought myself "Okay, so maybe the compiler adds it." However, after decompiling this assembly with dnSpy, ilSpy I don't see such attribute at all.
So bottom line questions are:
Is this attribute intended for usage by users of .NET framework or it is only the compiler applying it (given the fact its namespace)?
What does those flags mean in its constructor? I am not sure I understand what is prefix traversal of a type's construction.
The dynamic keyword in C# does not have a dedicated type in the metadata and at runtime, it is simply object. When you add a reference to the assembly then you like to see it back as dynamic. So does the C# compiler, necessary so it can auto-generate the binder code. The [Dynamic] attribute ensures that happens.
A decompiler would do the same thing so it doesn't have to show you this attribute either. Whatever tool you used to browse the metadata wasn't that smooth about it and revealed the detail.
The C# compiler emits the attribute automatically. Nothing you have to do yourself.
Can someone give me an example of the use of the RunCommand method that takes a string argument only (called CommandName) available in the MongoDB .NET driver? I know there is an overloaded RunCommand method that takes an object reference (I think a CommandDocument object) as an argument, but I'd rather not use that one.
I'm having trouble getting the syntax right for CommandName. Thanks in advance!
If you are using some recent version of the official C# driver, the "real" string based version you are referring to (CommandResult RunCommand(string commandName)) is only part of the legacy driver component (check the namespace). I would hence not recommend using it.
The "official" interface currently looks like this:
TResult RunCommand<TResult>(Command<TResult> command, /* and some additional optional parameters */)
And since the C# driver heavily relies on implicit type conversions, there also is one from a string (and a BsonDocument) to the corresponding sub types of Command<TResult> (JsonCommand<TResult> and BsonDocumentCommand<TResult>). So you can effectively pass a string to the above new RunCommand() method, too.
You can therefore write either one of the following lines both of which do the exact same thing:
RunCommand<BsonDocument>("{count: \"collection_name\"}")
RunCommand<BsonDocument>(new BsonDocument("count", "collection_name"))
I have a ComVisible COM class written in C#. I want to call it from another C# bit of code using COM and pass the default value for the parameter. I can call plenty of other methods without default arguments.
This is the best I can come up with. The first two lines work for all my other methods.
Type mytype = Type.GetTypeFromProgID("MyType");
dynamic myinstance = Activator.CreateInstance(mytype);
object missingValue = System.Reflection.Missing.Value;
myinstance.generatecsvdocument("mystring", ref missingValue);
My method looks like this:
public void generatecsvdocument(string mystring, string rowseperator = "\n")
When I run it I get the error:
The best overloaded method match for 'generatecsvdocument(string,
string)' has some invalid arguments
object missingValue = System.Reflection.Missing.Value;
That cannot work here. It is only valid for a COM method that takes a VARIANT as an argument. Looks like object or dynamic in C#. A very different kind of default argument mechanism than C# supports, it is the callee that determines the default value. In C# it is the caller that determines it, the C# compiler uses metadata to know that default.
Missing.Value turns in a variant of type vtError with the value DISP_E_PARAMNOTFOUND at runtime. Signalling the COM method to use the default value. Not actually that commonly used, usually only implemented in COM servers that support scripting languages. Office Automation is the most common example, probably what inspired you to try this.
But no, your argument is string, not a variant. There is no way to discover the default either when you use late binding, implicit is that you don't know anything about the default value stored in metadata. Otherwise the reason that the vtError mechanism exists, scripting languages have the same problem. The only real way to get ahead is to rewrite the method and test for a null argument, substituting "\n" if that's the case.
I am using Visual Studio 2010 with two projects.
One contains a project referencing Microsoft's Exchange.WebServices dll(ver1.2) for accessing ExchangeServices. I created a class which contains some helper methods and wrappers to carry out various tasks while connected to an Exchange server(through the ExchangeService API). The ExchangeService constructor can accept an enum of ExchangeVersion, to specify the server version info. So I created two constructors within my class.
public class ExchangeConnector(string ver)
{
// Property assignments
}
public class ExchangeConnector(ExchangeVersion ver)
:this(ver.toString()) //Using(or not using) "this", doesn't seem to matter...
{ }
I created the constructor which accepts a string parameter, so that other projects don't necessarily need to add the Exchange.WebServices library.
But then I ran into an un-foreseen issue.
When I create an instance of ExchangeConnector("Exchange2007_SP1") in my second project(that does not contain a reference to the Exchange.WebServices dll), Intellisense doesn't pick the right constructor and doesn't show any pre-compile errors. When I force the build, though, I get the following error:
Error: The type 'Microsoft.Exchange.WebServices.Data.ExchangeVersion' is defined
in an assembly that is not referenced. You must add a reference to assembly
'Microsoft.Exchange.WebServices, Version=14.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35'.
I am not even using the constructor with the ExchangeVersion enum reference, but it requires me to have a reference to it?
If I comment out the constructor with the ExchangeVersion enum, everything compiles, works, no run-time errors.
OR
If I modify the overload constructor so Intellisense can't possibly confuse the two, such as:
public class ExchangeConnector(string url, ExchangeVersion ver)
{
// Property assignments
}
When I call ExchangeConnector("Exchange2007_SP1"), the code compiles and works fine. No runtime errors.
It is almost as though VS can't resolve which constructor to properly use. Now I know I can add the reference to the second project and be done with it, but I am curious as to why VS is doing this. Any ideas?
After inspiration from Scott(the other one) and the link provided by CodeCaster, I think I finally found my answer.
It as part of the C# Language Specification(Visual Studio .Net 2003 Edition).
In Section 10.10 Instance Constructors:
An instance constructor initializer of the form base(argument-listopt) causes an instance constructor from the direct base class to be invoked. That constructor is selected using argument-list and the overload resolution rules of Section 7.4.2. The set of candidate instance constructors consists of all accessible instance constructors contained in the direct base class (including any default constructor, as defined in Section 10.10.4). If this set is empty, or if a single best instance constructor cannot be identified, a compile-time error occurs.
Diving down further to the definition...
In Section 7.4.2 Overload resolution:
Once the candidate function members and the argument list have been identified, the selection of the best function member is the same in all cases:
Given the set of applicable candidate function members, the best function member in that set is located.
If the set contains only one function member, then that function member is the best function member.
Otherwise, the best function member is the one function member that is better than all other function members with respect to the given argument list, provided that each function member is compared to all other function members using the rules in Section 7.4.2.2.
If there is not exactly one function member that is better than all other function members, then the function member invocation is ambiguous and a compile-time error occurs.
And even further...
Section 7.4.2.2 Better function member[3]
[3][http://msdn.microsoft.com/en-us/library/aa691338(v=vs.71).aspx]
Given an argument list A with a set of argument types {A1, A2, ..., AN} and two applicable function members MP and MQ with parameter types {P1, P2, ..., PN} and {Q1, Q2, ..., QN}, MP is defined to be a better function member than MQ if
for each argument, the implicit conversion from AX to PX is not worse than the implicit conversion from AX to QX, and
for at least one argument, the conversion from AX to PX is better than the conversion from AX to QX.
When performing this evaluation, if MP or MQ is applicable in its expanded form, then PX or QX refers to a parameter in the expanded form of the parameter list.
So in summary:
A constructor with the same number of arguments must be evaluated through implicit conversion as to which function member(constructor) would be better.
Hence why I needed to add the reference to the second project so the compiler could determine which constructor was better.
If the constructors don't have the same number of arguments, evaluation doesn't need to occur, and the compiler doesn't need the reference to the second project.
It makes sense to me that if your project is referencing another project with an overloaded constructor where one of those constructors accepts a referenced library, that your calling project would need that reference to compile even though you aren't using that particular constructor. Wouldn't the compiler need that reference to decide which constructor to use? Maybe I'm missing something, but it seems logical to me that it's working correctly.
Please, help me to explain the following behavior:
dynamic d = 1;
ISet<dynamic> s = new HashSet<dynamic>();
s.Contains(d);
The code compiles with no errors/warnings, but at the last line I get the following exception:
Unhandled Exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'System.Collections.Generic.ISet<object>' does not contain a definition for 'Contains'
at CallSite.Target(Closure , CallSite , ISet`1 , Object )
at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid2[T0,T1](CallSite site, T0 arg0, T1 arg1)
at FormulaToSimulation.Program.Main(String[] args) in
As far as I can tell, this is related to dynamic overload resolution, but the strange things are
(1) If the type of s is HashSet<dynamic>, no exception occurs.
(2) If I use a non-generic interface with a method accepting a dynamic argument, no exception occurs.
Thus, it looks like this problem is related particularly with generic interfaces, but I could not find out what exactly causes the problem.
Is it a bug in the compiler/typesystem, or legitimate behavior?
The answers you have received so far do not explain the behaviour you are seeing. The DLR should find the method ICollection<object>.Contains(object) and call it with the boxed integer as a parameter, even if the static type of the variable is ISet<dynamic> instead of ICollection<dynamic> (because the former derives from the latter).
Therefore, I believe this is a bug and I have reported it to Microsoft Connect. If it turns out that the behaviour is somehow desirable, they will post a comment to that effect there.
Why it compiles: the entire expression is evaluated as dynamic (hover your mouse over it inside your IDE to confirm), which means that it is a runtime check.
Why it bombs: My (completely wrong, see below) guess is that it is because you cannot implement a dynamic interface in such a manner. For example, the compiler does not allow you to create a class that implements ISet<dynamic>, IEnumerable<dynamic>, IList<dynamic>, etc. You get a compile-time error stating "cannot implement a dynamic interface". See Chris Burrows' blog post on this subject.
http://blogs.msdn.com/b/cburrows/archive/2009/02/04/c-dynamic-part-vii.aspx
However, since it's hitting the DLR anyway, you can make s completely dynamic.
dynamic s = new HashSet<dynamic>;
s.Contains(d);
Compiles and runs.
Edit: the second part of this answer is completely wrong. Well, it is correct in that you can't implement such an interface as ISet<dynamic>, but that's not why this blows up.
See Julian's answer below. You can get the following code to compile and run:
ICollection<dynamic> s = new HashSet<dynamic>();
s.Contains(d);
The Contains method is defined on ICollection<T>, not ISet<T>. The CLR doesn't allow an interface base method to be called from a derived interface. You usually doesn't see this with static resolution because the C# compiler is smart enough to emit a call to ICollection<T>.Contains, not the non-existing ISet<T>.Contains.
Edit: The DLR mimics the CLR behavior, that's why you get the exception. Your dynamic call is done on an ISet<T>, not an HashSet<T> the DLR will mimics the CLR: for an interface, only interfaces methods are searched for, not base interfaces (contrary to classes where this behavior is present).
For an in-depth explanation, see a previous response of mine to a similar question:
Strange behaviour when using dynamic types as method parameters
Note that the type dynamic doesn’t actually exist at run-time. Variables of that type are actually compiled into variables of type object, but the compiler turns all the method calls (and properties and everything) that involve such an object (either as the this object or as a parameter) into a call that is resolved dynamically at runtime (using System.Runtime.CompilerServices.CallSiteBinder and related magic).
So what happens in your case is that the compiler:
turns ISet<dynamic> into ISet<object>;
turns HashSet<dynamic> into HashSet<object>, which becomes the actual run-time type of the instance you’re storing in s.
Now if you try to invoke, say,
s.Contains(1);
this actually succeeds without a dynamic invocation: it really just calls ISet<object>.Contains(object) on the boxed integer 1.
But if you try to invoke
s.Contains(d);
where d is dynamic, then the compiler turns the statement into one that determines, at runtime, the correct overload of Contains to call based on the runtime type of d. Perhaps now you can see the problem:
The compiler emits code that definitely searches the type ISet<object>.
That code determines that the dynamic variable has type int at runtime and tries to find a method Contains(int).
ISet<object> does not contain a method Contains(int), hence the exception.
ISet interface does not have a method 'Contains', HashSet does however?
EDIT
What i meant to say was the binder resolves 'Contains' when given the HashSet concreate type, but doesnt find the inherited 'Contains' method in the interface...