C# Roslyn replace methods - c#

I want to refactor (add a prefix) local declared methods and their usage in .cs files
What is the best practice to accomplish that ?
My current code only deals with declarations
using System;
using System.IO;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace CodeScanner {
internal sealed class Fixer : CSharpSyntaxRewriter {
public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node) {
base.VisitInvocationExpression(node);
// replace usages
return node;
}
public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node) {
base.VisitMethodDeclaration(node);
return node.ReplaceNode(node, SyntaxFactory.MethodDeclaration(
node.AttributeLists,
node.Modifiers,
node.ReturnType,
node.ExplicitInterfaceSpecifier,
SyntaxFactory.Identifier("prefix_" + node.Identifier.Value),
node.TypeParameterList,
node.ParameterList,
node.ConstraintClauses,
node.Body,
node.ExpressionBody));
}
}
class Program {
static void Main(string[] args) {
var tree = CSharpSyntaxTree.ParseText(File.ReadAllText("./test.cs"));
var rewriter = new Fixer();
var result = rewriter.Visit(tree.GetRoot());
Console.WriteLine(result.ToFullString());
}
}
}
Input file
using System;
namespace TopLevel
{
class Bar {
public void test1(){}
public void test2(){ Console.WriteLine("str"); }
public void Fizz() {
Console.WriteLine(test1());
Console.WriteLine(test1(test2()));
test2();
}
}
}
Output
using System;
namespace TopLevel
{
class Bar {
public void prefix_test1(){}
public void prefix_test2(){ Console.WriteLine("str"); }
public void prefix_Fizz() {
Console.WriteLine(test1());
Console.WriteLine(test1(test2()));
test2();
}
}
}
Desired output (changes # Fizz ):
using System;
namespace TopLevel
{
class Bar {
public void prefix_test1(){}
public void prefix_test2(){ Console.WriteLine("str"); }
public void prefix_Fizz() {
Console.WriteLine(prefix_test1());
Console.WriteLine(prefix_test1(prefix_test2()));
prefix_test2();
}
}
}

I wrote some code that matches the requirements you set.
See on .NET Fiddle
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using static Globals;
tree = CSharpSyntaxTree.ParseText(File.ReadAllText("Test.cs"));
compilation = CSharpCompilation.Create("HelloWorld").AddSyntaxTrees(tree);
model = compilation.GetSemanticModel(tree);
new Finder().Visit(tree.GetRoot());
Console.WriteLine(new Rewriter().Visit(tree.GetRoot()).NormalizeWhitespace().ToFullString());
public static class Globals
{
public static SyntaxTree tree;
public static CompilationUnitSyntax root;
public static CSharpCompilation compilation;
public static SemanticModel model;
public static Dictionary<IMethodSymbol, string> renames = new();
}
public sealed class Finder : CSharpSyntaxWalker
{
public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
{
base.VisitMethodDeclaration(node);
renames.Add(model.GetDeclaredSymbol(node), "prefix_" + node.Identifier.Value);
}
}
public sealed class Rewriter : CSharpSyntaxRewriter
{
public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax mds)
{
IMethodSymbol symbol = model.GetDeclaredSymbol(mds);
mds = (MethodDeclarationSyntax)base.VisitMethodDeclaration(mds);
if (renames.TryGetValue(symbol, out string newName))
mds = mds.ReplaceToken(mds.Identifier, SyntaxFactory.Identifier(newName));
return mds;
}
[return: NotNullIfNotNull("node")]
public override SyntaxNode Visit(SyntaxNode node)
{
node = base.Visit(node);
if (node is SimpleNameSyntax sns &&
model.GetSymbolInfo(sns) is { Symbol: IMethodSymbol ms } && renames.TryGetValue(ms, out string newName)
)
node = sns.ReplaceToken(sns.Identifier, SyntaxFactory.Identifier(newName));
return node;
}
}

Related

How to get all methods in MEF

I have attribute class
[AttributeUsage(AttributeTargets.Method)]
public class MethodGetterAttribute : ExportAttribute
{
}
I'm using it in method of several namespaces:
namespace Model.First
{
public class PersonBL
{
[MethodGetter]
public void GetName(Person person)
{
}
}
}
namespace Model.First.Second
{
public class PersonBL
{
[MethodGetter]
public void GetName(Person person)
{
}
}
}
namespace Model.First.Second.Third
{
public class WorkerBL
{
[MethodGetter]
public void GetName(Worker worker)
{
}
}
}
I want to order all methods and run it one by one. To get methods I'm doing this:
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(assemblies.FirstOrDefault(a => a.GetName().Name.Contains("Model"))));
var container = new CompositionContainer(catalog);
var importedMethods = container.GetExports<Action<Worker>>() as IEnumerable<Lazy<Action<Worker>>>;
var result = importedMethods.Select(a => a.Value.Target).ToList();// Here i'm getting only worker's method
But it returns only Worker's method. How can I get all three methods from worker?
Well...
Let's create 4 class libraries
Zero.dll with all classes used in other assemblies
using System;
using System.ComponentModel.Composition;
using System.Diagnostics;
namespace Zero
{
[AttributeUsage(AttributeTargets.Method)]
public class MethodGetterAttribute : ExportAttribute { }
public class Person { }
public class Worker : Person { }
public static class MethodHelper
{
public static string GetMethod()
{
var method = new StackTrace().GetFrame(1).GetMethod();
return $"{method.DeclaringType.FullName} {method}";
}
}
public static class Discovery
{
public static TDelegate[] GetDelegates<TAttribure, TDelegate>()
where TAttribure : Attribute
where TDelegate : Delegate
{
return Directory.GetFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "*.dll")
.Select(file => { try { return Assembly.LoadFrom(file); } catch { return null; } })
.OfType<Assembly>()
.Append(Assembly.GetEntryAssembly())
.SelectMany(assembly => assembly.GetTypes())
.SelectMany(type => type.GetMethods())
.Where(method => method.GetCustomAttributes(typeof(TAttribure)).Any())
.Select(method => Delegate.CreateDelegate(typeof(TDelegate), null, method, false))
.OfType<TDelegate>()
.ToArray();
}
}
}
Model.First.dll referencing Zero.dll
using System;
using Zero;
namespace Model.First
{
public class PersonBL
{
[MethodGetter]
public void GetName(Person person)
{
Console.WriteLine(MethodHelper.GetMethod());
}
}
}
Model.First.Second.dll referencing Zero.dll
using System;
using Zero;
namespace Model.First.Second
{
public class PersonBL
{
[MethodGetter]
public void GetName(Person person)
{
Console.WriteLine(MethodHelper.GetMethod());
}
[MethodGetter]
public void Incompatible(string s)
{
Console.WriteLine(MethodHelper.GetMethod());
}
}
}
Model.First.Second.Third.dll referencing Zero.dll
using System;
using Zero;
namespace Model.First.Second.Third
{
public class WorkerBL
{
[MethodGetter]
public void GetName(Worker worker)
{
Console.WriteLine(MethodHelper.GetMethod());
}
public void NoAttribute(Worker worker)
{
Console.WriteLine(MethodHelper.GetMethod());
}
}
}
Then let's create console application ConsoleApp.exe referencing Zero.dll, Model.First.dll, Model.First.Second.dll and Model.First.Second.Third.dll
using System;
using Zero;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
var worker = new Worker();
foreach (var d in Discovery.GetDelegates<MethodGetterAttribute, Action<Worker>>())
d.Invoke(worker);
}
}
public class WorkerBL
{
[MethodGetter]
public void GetName(Worker worker)
{
Console.WriteLine(MethodHelper.GetMethod());
}
}
}
Let's create Junk.txt, put some nonsense like bd%E56#EVwD into it, rename the file to Junk.dll and add it into .exe file directory and then start the application.
Output is:
Model.First.PersonBL Void GetName(Zero.Person)
Model.First.Second.PersonBL Void GetName(Zero.Person)
Model.First.Second.Third.WorkerBL Void GetName(Zero.Worker)
ConsoleApp.WorkerBL Void GetName(Zero.Worker)
As expected. It finds all compatible methods with specified attribute and returns delegates for them.

Is there a way for me to access a C# class attribute?

Is there a way for me to access a C# class attribute?
For instance, if I have the following class:
...
[TableName("my_table_name")]
public class MyClass
{
...
}
Can I do something like:
MyClass.Attribute.TableName => my_table_name
Thanks!
You can use Attribute.GetCustomAttribute method for that:
var tableNameAttribute = (TableNameAttribute)Attribute.GetCustomAttribute(
typeof(MyClass), typeof(TableNameAttribute), true);
However this is too verbose for my taste, and you can really make your life much easier by the following little extension method:
public static class AttributeUtils
{
public static TAttribute GetAttribute<TAttribute>(this Type type, bool inherit = true) where TAttribute : Attribute
{
return (TAttribute)Attribute.GetCustomAttribute(type, typeof(TAttribute), inherit);
}
}
so you can use simply
var tableNameAttribute = typeof(MyClass).GetAttribute<TableNameAttribute>();
You can use reflection to get it. Here's is a complete encompassing example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication2
{
public class TableNameAttribute : Attribute
{
public TableNameAttribute(string tableName)
{
this.TableName = tableName;
}
public string TableName { get; set; }
}
[TableName("my_table_name")]
public class SomePoco
{
public string FirstName { get; set; }
}
class Program
{
static void Main(string[] args)
{
var classInstance = new SomePoco() { FirstName = "Bob" };
var tableNameAttribute = classInstance.GetType().GetCustomAttributes(true).Where(a => a.GetType() == typeof(TableNameAttribute)).Select(a =>
{
return a as TableNameAttribute;
}).FirstOrDefault();
Console.WriteLine(tableNameAttribute != null ? tableNameAttribute.TableName : "null");
Console.ReadKey(true);
}
}
}
Here's an extension that will make it easier, by extending object to give you an attribute helper.
namespace System
{
public static class ReflectionExtensions
{
public static T GetAttribute<T>(this object classInstance) where T : class
{
return ReflectionExtensions.GetAttribute<T>(classInstance, true);
}
public static T GetAttribute<T>(this object classInstance, bool includeInheritedAttributes) where T : class
{
if (classInstance == null)
return null;
Type t = classInstance.GetType();
object attr = t.GetCustomAttributes(includeInheritedAttributes).Where(a => a.GetType() == typeof(T)).FirstOrDefault();
return attr as T;
}
}
}
This would turn my previous answer into:
class Program
{
static void Main(string[] args)
{
var classInstance = new SomePoco() { FirstName = "Bob" };
var tableNameAttribute = classInstance.GetAttribute<TableNameAttribute>();
Console.WriteLine(tableNameAttribute != null ? tableNameAttribute.TableName : "null");
Console.ReadKey(true);
}
}

MEF plugin calling another plugin with same interface

I am trying to make a (my first MEF) system in which plugins can be recursive, i.e. my main system calls a MEF plugin with a standard interface, which on its own can then call another (or several) plugin(s), and so on.
When testing though, my plugin does not call the underlying plugin, but starts processing itself (creating a loop).
Any idea how I can prevent this?
Interface:
public interface IConnector
{
XDocument Run(object serviceCredentials, object connectorIds, object connectorKeys);
}
My main plugin inherits the interface, and defines the import for the next (The subplugin has the same definition):
[Export(typeof(IConnector))]
public class Connector : IConnector
{
[Import(typeof(IConnector))]
private IConnector connector;
....
The called plugin is initiated (in the Run method of the main plugin):
public XDocument Run(object serviceCredentials, object connectorIds, object connectorKeys)
{
string calledConnector = Path.Combine(AssemblyDirectory, "subplugin.dll");
AssemblyCatalog assembyCatalog = new AssemblyCatalog(Assembly.LoadFrom(calledConnector));
CompositionContainer container = new CompositionContainer(assembyCatalog);
container.ComposeParts(this);
....
The container should now contain just one plugin, the subplugin.dll.
I call the method 'Run' which is in the interface to invoke the subplugin method:
XDocument something = connector.Run(serviceCredentials, connectorids, connectorkeys);
But, instead of running the subplugin code, the 'Run' method in my main plugin activates, which keeps activating itself.
When I remove the [Export(typeof(iConnector)] in the main plugin, the subplugin is activated, but I want my main plugin to be able to be called in the same manner.
Being new to MEF I am stuck as to how to solve this. Any help would be much appreciated!
You should use Contracts and specify your intent, otherwise MEF will go into an infinite loop or pick the Connector as it exposes IConnector itself.
Some more info from MSDN.
For example
[Export("Container", typeof(IConnector))]
public class Connector : IConnector
{
[Import("Component", typeof(IConnector))]
private IConnector connector;
....
UPDATE
So after giving it some thought, here is an example of metadata based approach, and one that also limits the number of expensive catalog operations.
The IConnector
using System.Xml.Linq;
namespace Common
{
public interface IConnector
{
XDocument Run(object serviceCredentials, object connectorIds, object connectorKeys);
void Identify();
}
}
The metadata attribute ConnectorMetadata
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
namespace Common
{
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class)]
public class ConnectorMetadata : ExportAttribute
{
public string Name { get; private set; }
public ConnectorMetadata(string name):base(typeof(IConnector))
{
Name = name;
}
public ConnectorMetadata(IDictionary<string, object> metaData) : base(typeof (IConnector))
{
Name = Convert.ToString(metaData["Name"]);
}
}
}
The lazy singleton for PluginsCatalog
using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.IO;
using System.Linq;
using System.Reflection;
using Common;
namespace Common
{
public class PluginsCatalog
{
[ImportMany]
public Lazy<IConnector, ConnectorMetadata>[] Connectors;
private static readonly Lazy<PluginsCatalog> LazyInstance = new Lazy<PluginsCatalog>(() => new PluginsCatalog());
private PluginsCatalog()
{
var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? Directory.GetCurrentDirectory();
var directoryCatalog = new DirectoryCatalog(path, "*plugin.dll");
var aggregateCatalog = new AggregateCatalog(assemblyCatalog, directoryCatalog);
var container = new CompositionContainer(aggregateCatalog);
container.SatisfyImportsOnce(this);
}
public static PluginsCatalog Instance { get { return LazyInstance.Value; } }
public IConnector GetConnector(string name)
{
var match = Connectors.SingleOrDefault(s => s.Metadata.Name.Equals(name));
return match == null ? null : match.Value;
}
}
}
The "Primary" IConnector
using System;
using System.Xml.Linq;
using Common;
namespace Common
{
[ConnectorMetadata("Primary")]
public class Connector : IConnector
{
public XDocument Run(object serviceCredentials, object connectorIds, object connectorKeys)
{
PluginsCatalog.Instance.GetConnector("Sub").Identify();
return default(XDocument);
}
public void Identify()
{
Console.WriteLine(GetType().FullName);
}
}
}
The "Sub" IConnector
using System;
using System.Xml.Linq;
using Common;
namespace SubPlugin
{
[ConnectorMetadata("Sub")]
public class SubConnector:IConnector
{
public XDocument Run(object serviceCredentials, object connectorIds, object connectorKeys)
{
return default(XDocument);
}
public void Identify()
{
Console.WriteLine(GetType().FullName);
}
}
}
and finally the program itself:
namespace SOMEF
{
class Program
{
static void Main(string[] args)
{
var connector = PluginsCatalog.Instance.GetConnector("Primary");
connector.Identify();
connector.Run(null, null, null);
}
}
}
Which prints:
SOMEF.Connector
SubPlugin.SubConnector
Hope this helps ... :)
You might want to read this https://msdn.microsoft.com/en-us/library/ee155691(v=vs.110).aspx
With exmplanation how named exports are used
public class MyClass
{
[Import("MajorRevision")]
public int MajorRevision { get; set; }
}
public class MyExportClass
{
[Export("MajorRevision")] //This one will match.
public int MajorRevision = 4;
[Export("MinorRevision")]
public int MinorRevision = 16;
}

Inconsistent accessibility with protected internal member

Attempting to make a protected internal member of a protected internal class within a public class results with the following issue:
Inconsistent accessibility: field type
'what.Class1.ProtectedInternalClass' is less accessible than field
'what.Class1.SomeDataProvider.data'
The accessibility should be equivalent, as far as I know.
Where am I mistaken?
Origination class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace what
{
public class Class1
{
// This class cannot be modified, is only
// here to produce a complete example.
public class PublicClass
{
public PublicClass() { }
}
protected internal class ProtectedInternalClass : PublicClass
{
public ProtectedInternalClass() { }
public void SomeExtraFunction() { }
}
public class SomeDataProvider
{
public int AnInterestingValue;
public int AnotherInterestingValue;
protected internal ProtectedInternalClass data; //<--- Occurs here.
public PublicClass Data { get { return data; } }
}
public static SomeDataProvider RetrieveProvider()
{
SomeDataProvider provider = new SomeDataProvider();
provider.data = new ProtectedInternalClass();
provider.data.SomeExtraFunction();
return provider;
}
}
}
Verifying protected and internal properties, same assembly:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace what
{
public class Class2 : Class1
{
public Class2()
{
var pi = new ProtectedInternalClass();
var provider = new SomeDataProvider();
provider.data = pi;
}
// no errors here
}
public class Class3
{
public Class3()
{
var pi = new Class1.ProtectedInternalClass();
var provider = new Class1.SomeDataProvider();
provider.data = pi;
}
// no errors here
}
}
Verifying protected and internal properties, different assembly:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace some_other_assembly
{
public class Class4 : what.Class1
{
public Class4()
{
var pi = new ProtectedInternalClass();
var provider = new SomeDataProvider();
provider.data = pi;
}
// no errors here
}
public class Class5
{
public Class5()
{
var pi = new what.Class1.ProtectedInternalClass(); // <--- Inaccessible due to protection level, as it should be.
var provider = new what.Class1.SomeDataProvider();
provider.data = pi; // <--- Intellisense implies inaccessible, but not indicated via error.
}
}
}
The protected applies to different classes, and this can be seen with
class Derived : what.Class1.SomeDataProvider // note: Derived is not a nested class
{
public void f()
{
var data = this.data;
}
}
in a different assembly.
this.data has to be accessible, since the class derives from SomeDataProvider. Its type, ProtectedInternalClass, is not accessible, since the class does not derive from Class1.

Using ContextAttribute with a Method

The attribute which targets method is not working. The code is below. What could be the problem?
using System;
namespace AttributeProgram
{
class Program:ContextBoundObject
{
[TestAttribute("Hello")]
public void Print()
{
Console.WriteLine("How are you?");
}
static void Main(string[] args)
{
Program obj = new Program();
obj.Print();
}
}
[AttributeUsage(AttributeTargets.Method)]
class TestAttribute : System.Runtime.Remoting.Contexts.ContextAttribute
{
public TestAttribute(string Name) : base("Test")
{
Console.WriteLine(Name);
}
}
}
Because you're inheriting from ContextAttribute which can be applied only to classes, as per documentation:
[SerializableAttribute]
[ComVisibleAttribute(true)]
[AttributeUsageAttribute(AttributeTargets.Class)]
[SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.Infrastructure)]
[SecurityPermissionAttribute(SecurityAction.InheritanceDemand, Flags = SecurityPermissionFlag.Infrastructure)]
public class ContextAttribute : Attribute,
IContextAttribute, IContextProperty

Categories