Why are global using aliases not available in my unit test project? - c#

I have a Foo.csproj with a class in it: Utils.cs. In my Foo.Tests.csproj, I have a UtilsTest.cs. In Utils.cs, I have a global alias:
global using ProfileDataCollection =
System.Collections.Generic.IDictionary<string, TrashLib.Sonarr.ReleaseProfile.ProfileData>;
I was expecting ProfileDataCollection to now be usable everywhere, sort of like C++ typedefs. However, I'm not able to resolve this symbol from UtilsTest.cs. Do global usings not cross project-dependency boundaries?
I'm using .NET 6 + C# 10 with <ImplicitUsings> set to enable.

Related

My System.CommandLine app won't build! It can't find a CommandHandler. Do I need to write it?

I am using VS 2022, .Net 6.0, and trying to build my first app using System.CommandLine.
Problem: when I build it, I get an error
The name 'CommandHandler' does not exist in the current context
The code I'm trying to build is the sample app from the GitHub site: https://github.com/dotnet/command-line-api/blob/main/docs/Your-first-app-with-System-CommandLine.md , without alteration (I think).
It looks like this:
using System;
using System.CommandLine;
using System.IO;
static int Main(string[] args)
{
// Create a root command with some options
var rootCommand = new RootCommand
{
new Option<int>(
"--int-option",
getDefaultValue: () => 42,
description: "An option whose argument is parsed as an int"),
new Option<bool>(
"--bool-option",
"An option whose argument is parsed as a bool"),
new Option<FileInfo>(
"--file-option",
"An option whose argument is parsed as a FileInfo")
};
rootCommand.Description = "My sample app";
// Note that the parameters of the handler method are matched according to the names of the options
rootCommand.Handler = CommandHandler.Create<int, bool, FileInfo>((intOption, boolOption, fileOption) =>
{
Console.WriteLine($"The value for --int-option is: {intOption}");
Console.WriteLine($"The value for --bool-option is: {boolOption}");
Console.WriteLine($"The value for --file-option is: {fileOption?.FullName ?? "null"}");
});
// Parse the incoming args and invoke the handler
return rootCommand.InvokeAsync(args).Result;
}
I have installed the latest version of System.Commandline: 2.0.0-beta2.21617.1
SURELY I am just being a big fat idiot in some respect. But I don't see it.
Any insight would be welcomed.
This issue is caused by updating the CommandLine 2.0 Beta 2 package. Add the reference System.CommandLine.NamingConventionBinder to the references to fix the problem. Follow the announcements on command-line-api's GitHub account:
In your project, add a reference to System.CommandLine.NamingConventionBinder.
In your code, change references to the System.CommandLine.Invocation namespace to
use System.CommandLine.NamingConventionBinder, where the CommandHandler.Create
methods are now found. (There’s no longer a CommandHandler type in
System.CommandLine, so after you update you’ll get compilation errors until you
reference System.CommandLine.NamingConventionBinder.)
If you want to continue with the old habits, try using older versions of the System.CommandLine package.
References
Announcing System.CommandLine 2.0 Beta 2 and the road to GA
Think you are missing a using line:
using System;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.IO;
I can't swear that's it, but it looks like CommandHandler is defined in a namespace not referenced by a using (in your current code), so System.CommandLine.Invocation may be the key!

C# use same DLL (assembly) with and without "extern alias"

I am basically doing the same work as described in extern alias with same assembly file name: I am writing a converter for classes between different versions of a software, so that the XML settings of these classes can be converted.
The conversion is ready and working ("extern alias" are set where needed).
Now I want to change my small test project into a full program. The conversion uses the DLLs of V22, V23 and V24, each with respective alias, and the program should use the latest version of the DLLs (currently V24, without alias, thus global) for its own operation. The problem is, that the program does not find any types from the referenced global DLLs.
Is Visual Studio (I'm using v.2015 U3) maybe not able to distinguish between the DLLs if the same DLL is used with and without alias?
The project references:
basics, path=..\v24.., no alias (#1)
basics v=22, path=..\v22.., alias=V22
basics v=23, path=..\v23.., alias=V23
basics v=24, path=..\v24.., alias=V24 (#1)
imaging v=22, path=..\v22.., alias=V22
imaging v=23, path=..\v23.., alias=V23
imaging v=24, path=..\v24.., alias=V24
...
I supspect that the marked (#1) assemblies collide somehow.
Is this correct?
Any solution or workaround?
I could add "extern alias V24" in every file of the general part of the program, but then I'd have to change that to "extern alias V25" when the next version of the DLLs is released. I'd like to avoid that extra work.
I found an acceptable workaround.
Instead of adding the alias to every "using" when I switch to another version, e.g. replace "/* v24 */" by "swcore_v_0_22"
using CImageObject_V_0_22 = swcore_v_0_22.SwCore.CImageObject;
using CImageObject_V_0_24 = /* v24 */ SwCore.CImageObject;
using CImageObjectStandard_V_0_22 = swcore_v_0_22.SwCore.CImageObjectStandard;
using CImageObjectStandard_V_0_24 = /* v24 */ SwCore.CImageObjectStandard;
using CImageObjectCombi_V_0_22 = swcore_v_0_22.SwCore.CImageObjectCombination;
using CImageObjectCombi_V_0_24 = /* v24 */ SwCore.CImageObjectCombination;
I can simply add a nested using, which is not possible by default but possible when placed inside a different namespace, see https://stackoverflow.com/a/35921944/2505186. The namespace exists anyway.
using SwCore_v_0_22 = swcore_v_0_22.SwCore;
using SwCore_v_0_24 = global::SwCore;
namespace ConfigEditor
{
using CImageObject_V_0_22 /**/= SwCore_v_0_22.CImageObject;
using CImageObject_V_0_24 /**/= SwCore_v_0_24.CImageObject;
using CImageObjectStandard_V_0_22 /**/= SwCore_v_0_22.CImageObjectStandard;
using CImageObjectStandard_V_0_24 /**/= SwCore_v_0_24.CImageObjectStandard;
using CImageObjectCombi_V_0_22 /**/= SwCore_v_0_22.CImageObjectCombination;
using CImageObjectCombi_V_0_24 /**/= SwCore_v_0_24.CImageObjectCombination;
Later I will replace "global" by "swcore_v_0_24" in all files, which still is some work, but much less than before. And since it is replacing instead of adding, it can do it automatically.
I could theoretically also replace "/* v24 */", but that could break the nice vertical alignment depencing of the length of the replacement. ;-)

Python for .NET: Using same .NET assembly in multiple versions

My problem: I have an assembly in 2 versions and want to use them at the same time in my Python project.
The .NET libs are installed in GAC (MSIL), having the same public token:
lib.dll (1.0.0.0)
lib.dll (2.0.0.0)
In Python I want something like that:
import clr
clr.AddReference("lib, Version=1.0.0.0, ...")
from lib import Class
myClass1 = Class()
myClass1.Operation()
*magic*
clr.AddReference("lib, Version=2.0.0.0, ...")
from lib import class
myClass2 = Class()
myClass2.Operation()
myClass2.OperationFromVersion2()
*other stuff*
# both objects should be accessibly
myClass1.Operation()
myClass2.OperationFromVersion2()
Is there a way to do that? Something with AppDomains or bindingRedirect?
Note: Of course myClass1.operationFromVersion2() can fail...
Well I found a solution: Python for .NET also supports Reflection!
Instead of
clr.AddReference("lib, Version=1.0.0.0, ...")
You have to use
assembly1 = clr.AddReference("lib, Version=1.0.0.0, ...")
With that assembly you can use all the Reflection stuff like in C#. In my example I have to use following code (same for version 2):
from System import Type
type1 = assembly1.GetType(...)
constructor1 = type1.GetConstructor(Type.EmptyTypes)
myClass1 = constructor1.Invoke([])
I could not get it working using the accepted answer. Here is my solution.
Instead of using PythonNet you must use the .NET framework directly:
import System
dll_ref = System.Reflection.Assembly.LoadFile(fullPath)
print(dll_ref.FullName)
print(dll_ref.Location)
Check that the correct DLL is used.
To use multiple DLLs with the same version just load it to another variable
another_dll_ref = System.Reflection.Assembly.LoadFile(anotherFullPath)
Now you can use objects from the specified dll.
Instance of a public non-static class
some_class_type = dll_ref.GetType('MyNamespace.SomeClass')
my_instance = System.Activator.CreateInstance(some_class_type)
my_instance.a = 4 # setting attribute
my_instance.b('whatever') # calling methods
Calling a method in a public static class
some_class_type = dll_ref.GetType('MyNamespace.SomeClass')
method = some_class_type.GetMethod('SomeMethod')
# return type and list of parameters
method.Invoke(None, [1, 2.0, '3'])
Creating a instance of a struct
some_struct_type = dll_ref.GetType('MyNamespace.SomeStruct')
my_struct = System.Activator.CreateInstance(some_struct_type)
my_struct.a = 3
(taken from my question Python for .NET: How to explicitly create instances of C# classes using different versions of the same DLL?)

How to invoke C#/.NET namespace in IronPython?

I'm looking to replicate the following in IronPython and searching has so far been fruitless and/or disappointing.
namespace Groceries
{
public class ChocolateMilk : Milk
{
// Other stuff here
}
}
The idea would be that the compiled Python DLL will be loaded into a C# program through System.Reflection.Assembly.Load and a GetType("Groceries.ChocolateMilk") on the loaded DLL would not return null.
The most recent answer I was able to find was in 2008 and said that it was impossible without using the Hosting API - http://lists.ironpython.com/pipermail/users-ironpython.com/2008-October/008684.html.
Any suggestions on how to accomplish this would be greatly appreciated. Any conclusions that this is currently impossible to do via IronPython will also be appreciated, but less so.
I'm a bit confused on what you're asking here. Are you trying to instantiate that C# code in your IronPython modules? Or do you have the equivalent classes written in IronPython and you want to instantiate them in your C# code?
Based on the link you posted, I suppose you're going for the latter and have IronPython classes that you want instantiated in your C# code. The answer is, you cannot directly instantiate them. When you compile IronPython code to an assembly, you cannot use the types defined there with your regular .NET code since there is not a one-to-one mapping between IronPython classes and .NET classes. You would have to host the assembly in your C# project and instantiate it that way.
Consider this module, Groceries.py compiled to Groceries.dll residing in the working directory:
class Milk(object):
def __repr__(self):
return 'Milk()'
class ChocolateMilk(Milk):
def __repr__(self):
return 'ChocolateMilk()'
To host the module in your C# code:
using System;
using IronPython.Hosting;
using System.IO;
using System.Reflection;
class Program
{
static void Main(string[] args)
{
var engine = Python.CreateEngine();
var groceriesPath = Path.GetFullPath(#"Groceries.dll");
var groceriesAsm = Assembly.LoadFile(groceriesPath);
engine.Runtime.LoadAssembly(groceriesAsm);
dynamic groceries = engine.ImportModule("Groceries");
dynamic milk = groceries.ChocolateMilk();
Console.WriteLine(milk.__repr__()); // "ChocolateMilk()"
}
}
Otherwise to go the other way and create an instance of your .NET type in your IronPython code (as your title suggests). You'd need to add the path to your assembly, reference it, then you could instantiate it as needed.
# add to path
import sys
sys.path.append(r'C:\path\to\assembly\dir')
# reference the assembly
import clr
clr.AddReferenceToFile(r'Groceries.dll')
from Groceries import *
chocolate = ChocolateMilk()
print(chocolate)

How to create an ActiveX control in C#?

I am not able to create a functioning ActiveX control in C#; I have tried following tutorials to do so without success.
I create a sample Class Library project which includes this code:
namespace AACWCSurvey
{
[ProgId("Prisoner.PrisonerControl")]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class Class1
{
public Class1()
{
MessageBox.Show("FIRETRUCK!!!");
}
}
}
I then did the following steps:
Properties => Application => Assembly Information => Make Assembly COM-visible
Build => Register for COM interop TRUE (checked)
Make Strong name for assembly (signing)
Build the project
regasm MyDll.dll /tlb /codebase
Can't see Prisoner.PrisonerControl in tstcon32 =(
My OS is WinXP x86.
UPD: it works from VBScript:
Dim objJava
Set objJava = WScript.CreateObject("Prisoner.PrisonerControl")
but it is not visible in tstcon32.
If you read the actual article using the Prisoner.PrisonerControl control a sub key named Control is created inside the key with your control GUID.
On my machine with the guid {9DEA5F06-E324-31A7-837B-D0F3BDE91423} creating the key
HKEY_CLASSES_ROOT\CLSID\{9DEA5F06-E324-31A7-837B-D0F3BDE91423}\Control
Make the control appears in tstcon32. And with or without it the ActiveX is usable for javascript
var x = new ActiveXControl("Prisoner.PrisonerControl");
Actually i had to fight windows on both the javascript execution and registry path to test it on my system because it's an x64 machine but that's another story.
You have created a COM server but not an ActiveX control, which is a far more intricate COM object, the kind that you can exercise with tstcon32.exe.
It must implement a bunch of interfaces, key ones are IOleObject and IOleWindow. The kind of interfaces that allows it to do the required negotiations with an ActiveX host and create a visible window. The Winforms Control class is your best bet to create one.
Here are the relevant steps as documented externally. This is summarized leaving out some exposition but not any necessary steps.
This example is also very similar to the article Using Managed Controls as ActiveX Controls by Garry Trinder, November 25, 2008 and I've included some notes from this article as well.
Exposing Windows Forms Controls as ActiveX controls
This article will describe how to utilise Windows Forms controls
outside of .NET.
Writing the control
Create a new control project from within Visual Studio - my examples are all in C# but VB.NET could also be used.
[Here Garry's article suggests, "First, create a managed usercontrol project – either a Windows Forms class library or control library project. Use the usercontrol designer to design your custom usercontrol the way you want it (using any standard controls you like)."]
Add controls etc to the form, put in the code etc.
Add in the following using clauses...
using System.Runtime.InteropServices;
using System.Text;
using System.Reflection;
using Microsoft.Win32;
Attribute your class so that it gets a ProgID. This isn't strictly necessary as one will be generated, but it's almost always best to be
explicit.
[ProgId("Prisoner.PrisonerControl")]
[ClassInterface(ClassInterfaceType.AutoDual)]
This assigns the ProgID, and also defines that the interface
exposed should be 'AutoDual' - this crufts up a default interface for
you from all public, non-static members of the class. If this isn't
what you want, use one of the other options.
Update the project properties so that your assembly is registered for COM interop.
If you're using VB.NET, you also need a strong named assembly.
Curiously in C# you don't - and it seems to be a feature of the
environment rather than a feature of the compiler or CLR.
Add the following two methods into your class.
[ComRegisterFunction()]
public static void RegisterClass ( string key )
{
// Strip off HKEY_CLASSES_ROOT\ from the passed key as I don't need it
StringBuilder sb = new StringBuilder ( key ) ;
sb.Replace(#"HKEY_CLASSES_ROOT\","") ;
// Open the CLSID\{guid} key for write access
RegistryKey k = Registry.ClassesRoot.OpenSubKey(sb.ToString(),true);
// And create the 'Control' key - this allows it to show up in
// the ActiveX control container
RegistryKey ctrl = k.CreateSubKey ( "Control" ) ;
ctrl.Close ( ) ;
// Next create the CodeBase entry - needed if not string named and GACced.
RegistryKey inprocServer32 = k.OpenSubKey ( "InprocServer32" , true ) ;
inprocServer32.SetValue ( "CodeBase" , Assembly.GetExecutingAssembly().CodeBase ) ;
inprocServer32.Close ( ) ;
// Finally close the main key
k.Close ( ) ;
}
The RegisterClass function is attributed with ComRegisterFunction -
this static method will be called when the assembly is registered for
COM Interop. All I do here is add the 'Control' keyword to the
registry, plus add in the CodeBase entry.
CodeBase is interesting - not only for .NET controls. It defines a URL
path to where the code can be found, which could be an assembly on
disk as in this instance, or a remote assembly on a web server
somewhere. When the runtime attempts to create the control, it will
probe this URL and download the control as necessary. This is very
useful when testing .NET components, as the usual caveat of residing
in the same directory (etc) as the .EXE does not apply.
[ComUnregisterFunction()]
public static void UnregisterClass ( string key )
{
StringBuilder sb = new StringBuilder ( key ) ;
sb.Replace(#"HKEY_CLASSES_ROOT\","") ;
// Open HKCR\CLSID\{guid} for write access
RegistryKey k = Registry.ClassesRoot.OpenSubKey(sb.ToString(),true);
// Delete the 'Control' key, but don't throw an exception if it does not exist
k.DeleteSubKey ( "Control" , false ) ;
// Next open up InprocServer32
RegistryKey inprocServer32 = k.OpenSubKey ( "InprocServer32" , true ) ;
// And delete the CodeBase key, again not throwing if missing
k.DeleteSubKey ( "CodeBase" , false ) ;
// Finally close the main key
k.Close ( ) ;
}
The second function will remove the registry entries added when (if)
the class is unregistered - it's always a good suggestion to tidy up
as you go.
Now you are ready to compile & test your control.
Additional notes from Garry's blog:
[The] additional registry entries: Control, MiscStatus, TypeLib and
Version [can be created] with a .REG script, but it’s generally better
to write functions that will be called on registration/unregistration
He describes the registry keys in some detail:
Control is an empty subkey. TypeLib is mapped to the GUID of the
TypeLib (this is the assembly-level GUID in the assemblyinfo.cs).
Version is the major and minor version numbers from the assembly
version. The only mildly interesting subkey is MiscStatus. This needs
to be set to a value composed of the (bitwise) values in the OLEMISC
enumeration, documented here. To make this enum available, add a
reference to Microsoft.VisualStudio.OLE.Interop (and a suitable
‘using’ statement for the namespace).
His final note is a warning:
Note: this seems to work OK for Excel (with the very limited testing
I've done), partly works with PowerPoint, but fails miserably with
Word. Possibly, some more of the OLEMISC values might improve this;
possibly there are some messages we need to hook; possibly there are
some more interfaces we need to implement ... The fact that I’ve only
barely got it to work in a very limited way should tell you that this
is probably not a technique you want to use in any serious way.

Categories

Resources