How to find a method that matches declaration rules? - c#

I need to find methods that match certain rules such as
has to be have return type void
has to be named "Set"
has to accept only one parameter
parameter type needs to match the type provided
I started going down the following route, but is just seems too much code. I wonder if there is a better way?
//find type name of the property
foreach (var propertySymbol in propertiesSymbols)
{
var propertyTypeSyntax =
((PropertyDeclarationSyntax) propertySymbol.DeclaringSyntaxNodes.First()).Type;
var propertyTypeInfo = semanticModel.GetTypeInfo(propertyTypeSyntax);
//find method with a name Set that accepts this type of the property
var allSetMethodsSymbols = classSymbol.GetMembers()
.Where(m => m.Kind == CommonSymbolKind.Method && m.Name.Equals("Set"))
.ToList();
foreach (var setMethodSymbol in allSetMethodsSymbols)
{
var methodDeclarationSyntax =
((MethodDeclarationSyntax) setMethodSymbol.DeclaringSyntaxNodes.First());
var expressionSyntax =
methodDeclarationSyntax.DescendantNodes().OfType<ExpressionSyntax>().First();
var typeInfo = semanticModel.GetTypeInfo(expressionSyntax);
var typeName = typeInfo.Type.Name;
if (typeName == "Void")
{
//now we know it is a method named "Set" and has return type "Void"
//let's see if parameter matches
var parameterSymbols =
methodDeclarationSyntax.DescendantNodes().OfType<ParameterSyntax>()
.ToList();
if (parameterSymbols.Count() == 1)
{
//this one has one parameter
//now let's see if it is of the type needed
var exprSyntax = ((ParameterSyntax) parameterSymbols.First())
.DescendantNodes().OfType<ExpressionSyntax>().First();
var parameterTypeInfo = semanticModel.GetTypeInfo(exprSyntax);
if (parameterTypeInfo.Type.Equals(propertyTypeInfo.Type))
{
//execute method rewriter
}
}
}
}
}
Solution as suggested by Jason:
var propertyTypeInfo = propertySymbol.Type;
//find method with a name Set that accepts this type of the property
IEnumerable<MethodSymbol> allSetMethodsSymbols = classSymbol
.GetMembers()
.Where(m =>m.Kind == CommonSymbolKind.Method && m.Name.Equals("Set"))
.Cast<MethodSymbol>();
var setMethod = allSetMethodsSymbols
.Single(x => x.ReturnsVoid
&& x.Parameters.Count == 1
&& x.Parameters.First().Type == propertyTypeInfo);

Yes, you're switching back and forth between our symbol model and syntax, which is making this more difficult than it needs to be. Cast those symbol objects you are getting from GetMembers to MethodSymbol (once checking that they're a method). Once you've got the MethodSymbol, you can just check the .ReturnType property to get the return type -- don't go to syntax and re-get it that way. Or, just use the handy .ReturnsVoid property for your scenario. Similarly, MethodSymbol has a .Parameters property you can use to get the parameters -- don't go back to syntax for that either.

Related

check whether a given property type is a typeof(list<>)

I have three lists something like List<EmpRoles> , List<EmpVisibility> , List<EmpProps>.
Now I want to perform certain operations on them. For this first I have to check whether the property is of type list or not.
I have use if block something like below
if ( propertyName == "EmpRoles" || propertyName == "EmpVis" || propertyName == "EmpProps")
Is there any better way of doing this thing , or is it possible to put some typeof(list<>) conditions. I know typeof(list<>) won't work here. Either i have to use typeof(list)...
Can Someone help in making a generic way to identify the list type properties?
Checking that a type is a List<T> requires ensuring that:
the type is generic by checking its IsGenericType property,
the generic type base is System.List<> by checking the result of GetGenericTypeDefinition() against typeof(List<>)
type T is the one you want by checking GetGenericArguments()
If all three conditions are met, you have your type:
var pt = myProperty.PropertyType;
if (pt.IsGenericType &&
pt.GetGenericTypeDefinition() == typeof(List<>)) {
var elementType = pt.GetGenericArguments()[0];
if (elementType == typeof(EmpRoles)) {
...
} else if (elenentType == typeof(EmpVisibility)) {
...
} else if ...
}
You can get generic parameter from list as following
var list = new List<EmpRoles>
var argType = list.GetType().GenericTypeArguments[0];
Here in argType you will get typeof(EmpRoles)

How can I get the most fully reduced type name of an arbitrary type in a given namespace context using Roslyn?

I am writing a function that takes any concrete or constructed Type, say for example typeof(ValueTuple<Nullable<System.Int32>, double, List<string>>) and returns a string that is the reduced C# syntax representation of that type (i.e. (int?, double, List<string>) in this example).
Here's what I have so far:
public static string ToCSharpString(this Type type, string[] usingNamespaces = null, Assembly[] usingAssemblies = null)
{
var compilationUnit = SyntaxFactory.CompilationUnit();
if (usingNamespaces != null)
{
compilationUnit = compilationUnit.AddUsings(
Array.ConvertAll(usingNamespaces, n => SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(n))));
}
else
{
compilationUnit = compilationUnit.AddUsings(
SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName("System")));
}
MetadataReference[] metadataReferences;
if (usingAssemblies != null)
{
metadataReferences = Array.ConvertAll(usingAssemblies, u => MetadataReference.CreateFromFile(u.Location));
}
else
{
metadataReferences = new[]
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(type.Assembly.Location)
};
}
TypeSyntax typeName;
using (var provider = new CSharpCodeProvider())
{
typeName = SyntaxFactory.ParseTypeName(provider.GetTypeOutput(new CodeTypeReference(type)));
}
var field = SyntaxFactory.FieldDeclaration(
SyntaxFactory.VariableDeclaration(typeName).WithVariables(
SyntaxFactory.SingletonSeparatedList<VariableDeclaratorSyntax>(
SyntaxFactory.VariableDeclarator(
SyntaxFactory.Identifier("field")))));
compilationUnit = compilationUnit.AddMembers(
SyntaxFactory.ClassDeclaration("MyClass").AddMembers(
field))
.NormalizeWhitespace();
var tree = compilationUnit.SyntaxTree;
var compilation = CSharpCompilation.Create("MyAssembly", new[] { tree }, metadataReferences);
var semanticModel = compilation.GetSemanticModel(tree);
var root = tree.GetRoot();
var typeSymbol = semanticModel.GetDeclaredSymbol(compilationUnit
.DescendantNodes().OfType<ClassDeclarationSyntax>().Single()
.Members.OfType<FieldDeclarationSyntax>().Single()
.Declaration.Type);
return typeSymbol.ToDisplayString(new SymbolDisplayFormat(
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes));
}
I'm attempting to string together a couple of known methods for converting types.
Type -> Fully Qualified Name
via CodeDOM's CSharpCodeProvider.GetTypeOutput
Fully Qualified Name -> TypeSyntax
via Rosly's SyntaxFactory.ParseTypeName
Now I would like to use .ToDisplayString() with a few different options, but I can't find a type node for my type that doesn't return null from the semantic model.
How can I format a TypeSyntax using a SymbolDisplayFormat?
In addition, I expect that this will change System.Int32 -> int, however, it won't automatically fix instances of Nullable<T> or ValueTuple<T1...>
How can I execute the appropriate code analysis rules to replace these type names?
The docs state GetDeclaredSymbol only works for
any type derived from MemberDeclarationSyntax, TypeDeclarationSyntax, EnumDeclarationSyntax, NamespaceDeclarationSyntax, ParameterSyntax, TypeParameterSyntax, or the alias part of a UsingDirectiveSyntax
Yours appears to be QualifiedNameSyntax which seems pretty much in line with the expected input but clearly means something else to Roslyn (I will admit I didn't bother checking whether it actually inherits from one of the expected types).
However, getting TypeInfo instead, seems to get your particular example working:
var typeSymbol = semanticModel.GetTypeInfo(compilationUnit // just changed this method
.DescendantNodes().OfType<ClassDeclarationSyntax>().Single()
.Members.OfType<FieldDeclarationSyntax>().Single()
.Declaration.Type);
return typeSymbol.Type.ToDisplayString(new SymbolDisplayFormat(
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes)); // I'm getting "(int?, double, List)" here

Modifying function declaration parameter by adding attribute and default value in Roslyn c#

I'm modifying my earlier code analyser in C# using Roslyn and again I'm stuck with some changes that I don't exactly know how to apply.
Basing on: https://github.com/dotnet/roslyn/wiki/Getting-Started-C%23-Syntax-Analysis I've created some base to work with in this question: Finding all not inheriting C# classes with Roslyn and changing to inheriting from base object (java-like)
I've parsed and traversed tree to find all method declarations, and their parameters. Basing on VS Syntax Visualiser I've constructed this:
foreach (var c in root1.DescendantNodesAndSelf())
{
var methodDeclaration = c as MethodDeclarationSyntax;
if (methodDeclaration == null)
continue;
if (methodDeclaration.ParameterList != null) //Have parameters
{
foreach (var p in methodDeclaration.ParameterList.Parameters)
{
var parameter = p as ParameterSyntax;
String name, type;
name = parameter.GetLastToken().Value.ToString();
type = parameter.GetFirstToken().Value.ToString();
if (parameter == null)
continue;
if (name == "caller" && type == "string")
{
AttributeSyntax ats = SyntaxFactory.Attribute(SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System.Runtime.CompilerServices"),SyntaxFactory.IdentifierName("CallerMemberName")));
SeparatedSyntaxList<AttributeSyntax> ssl = new SeparatedSyntaxList<AttributeSyntax>();
ssl = ssl.Add(ats);
AttributeListSyntax als = SyntaxFactory.AttributeList(ssl);
var par1 = parameter.AddAttributeLists(als);
//ExpressionSyntax es = SyntaxFactory.AssignmentExpression(SyntaxKind.EqualsValueClause,null,SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression))
//SyntaxFactory.EqualsValueClause(es);
par1 = par1.AddModifiers();
root2 = root2.ReplaceNode(parameter, par1);
}
}
}
else //Don't have parameters
continue;
}
I'm trying to convert method declared like this:
private void testM3(string caller)
into
private void testM3([System.Runtime.CompilerServices.CallerMemberName] string caller = "")
And this part:
//ExpressionSyntax es = SyntaxFactory.AssignmentExpression(SyntaxKind.EqualsValueClause,null,SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression))
//SyntaxFactory.EqualsValueClause(es);
is my failed attempt to achieve creation of equals node.
From what I understand, this part:
AttributeSyntax ats = SyntaxFactory.Attribute(SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System.Runtime.CompilerServices"),SyntaxFactory.IdentifierName("CallerMemberName")));
SeparatedSyntaxList<AttributeSyntax> ssl = new SeparatedSyntaxList<AttributeSyntax>();
ssl = ssl.Add(ats);
AttributeListSyntax als = SyntaxFactory.AttributeList(ssl);
var par1 = parameter.AddAttributeLists(als);
will give me new parameter node in var par1 that includes attribute already, so I need to add default value setting. Please correct me if I'm wrong about this attribute, and I'd like to know how to build this equals expression node properly.
You have a two mistakes:
System.Runtime.CompilerServices.CallerMemberName is QualifiedName that contains System.Runtime.CompilerServices as QualifiedName and CallerMemberName as IdentifierName. System.Runtime.CompilerServices contains System.Runtime as QualifiedName and CompilerServices as IdentifierName. And finally System.Runtime contains two IdentifierName.
So you need to fix creation of AttributeSyntax as are shown in the code below:
AttributeSyntax ats = SyntaxFactory.Attribute(
SyntaxFactory.QualifiedName(
SyntaxFactory.QualifiedName(
SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Runtime")),
SyntaxFactory.IdentifierName("CompilerServices")),
SyntaxFactory.IdentifierName("CallerMemberName")));
EqualsValueClause shouldn't contain AssignmentExpression, but should contains a some kind of LiteralExpression directly. In your case it's StringLiteralExpression:
var par1 = parameter
.AddAttributeLists(als)
.WithDefault(SyntaxFactory.EqualsValueClause(SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(""))));
By the way, you can use a some helpful method of Nodes, as example ParameterSyntax.WithDefault, to create a copy of Node (SyntaxTree is immutable in Roslyn) when you want to applay a small changes to existing node and then repace it.
If you take a look at the Roslyn Quoter for the following method you can obtain the code required to generate the desired code:
public void GetSomething([CallerMemberName] string test=""){
}
You will notice that the Default Value in the Parameter is constructed using the following method (the roslyn quoter usually omits the SyntaxFactory):
.WithDefault(SyntaxFactory.EqualsValueClause(
SyntaxFactory.LiteralExpression(
SyntaxKind.StringLiteralExpression,
SyntaxFactory.Literal("")
)
)
);
So in order to add your EqualsValueClause as a default you just have to replace the existing default by calling the above code on your parameter (instead of the uncommented code):
par1 = par1.WithDefault(SyntaxFactory.EqualsValueClause(
SyntaxFactory.LiteralExpression(
SyntaxKind.StringLiteralExpression,
SyntaxFactory.Literal("")
)
)
);

How to get arguments of method to make completion with Roslyn?

I try to make code completion with Roslyn and AvalonEdit.
For example, user have code:
public void Completion(int i,int j) { }
And he types:
Completion(
So, i want to get arguments of method (int i, int j) and make code completion.
I write simple code, wich works with '.' and may be this code will work for '(' letter?
public List<ICompletionData> GetCompletionData(String code,int offset,CompletionType completionType)
{
var syntaxTree = SyntaxFactory.ParseSyntaxTree(code);
var compilation = CSharpCompilation.Create("foo")
.AddReferences(Mscorlib)
.AddSyntaxTrees(syntaxTree);
var semanticModel = compilation.GetSemanticModel(syntaxTree);
var textSpan = GetTextSpan(offset,1);// '.' or '(' coordinates
ITypeSymbol lhsType = null;
if (completionType == CompletionType.DotCompletion)
{
var memberAccessNode = (MemberAccessExpressionSyntax)syntaxTree.GetRoot()
.DescendantNodes(textSpan).Last();
lhsType = semanticModel.GetTypeInfo(memberAccessNode.Expression).Type;
}
else if(completionType==CompletionType.ArgumentListCompletion)
{
var arr = syntaxTree.GetRoot().DescendantNodes(textSpan).Last();
var argumentListMode = (ArgumentListSyntax)syntaxTree.GetRoot().DescendantNodes(textSpan).Last();
var directive = argumentListMode.GetFirstDirective();
var arrgs=argumentListMode.Arguments;
//lhsType = semanticModel.GetTypeInfo(directive).Type;
//how to get lhsType?
}
if (lhsType == null)
return new List<ICompletionData>();
List<ICompletionData> completionDataColl = new List<ICompletionData>();
// and here i make completion data
foreach (var symbol in lhsType.GetMembers())
{
if (!symbol.CanBeReferencedByName
|| symbol.DeclaredAccessibility != Accessibility.Public
|| symbol.IsStatic)
continue;
}
}
The problem is, that i can not get ITypeSymbol lhsType. It is null.
How to get lhsType?
Or, maybe i should use another way?
I don't know the code completion (I couldn't find this class called CompletionType) itself, but here is a way based on Roslyn only: semantic model and the method invocation, which I believe you have available (make the method call string a InvocationExpressionSyntax)
To obtain the arguments of a method you can get its SymbolInfo from the semantic model. Then you get its symbol. A symbol contains the list of parameters(arguments).
You can call SemanticModel.GetSymbolInfo()
The result will provide you a symbol or candidate symbols (if it is an overloaded method).
A method symbol will provide the list of parameters, which is the arguments of that method.

Type "object {System.Collections.Generic.List<T>}" VS "System.Collections.Generic.List<T>"

I am using reflection to get a List and trying to pass it into a delegate that receives a List.
However, when I use reflection the type of my list is:
object {System.Collections.Generic.List<T>}
And when I pass it to the delegate (of generics) I get an exception because it is expecting the type:
System.Collections.Generic.List<T>
Just to confirm that this really the problem, I made a direct cast to List<RealTClass> and it worked. But, in my code I do not want to make this unnecessary cast... and also because I am using generics.
Question #1: Why the reflection returns the object as type: object { X } ?
Question #2: How can I "remove" the object { X } part from the type? Basically, I need a solution for this problem....
Thanks.
UPDATE #1: some code...
//METHOD receives 'obj' and 'includes'
T obj
Expression<Func<T, object[]>> includes = null
...
if (res && includes != null)
{
var array = includes.Body as NewArrayExpression;
if (array != null)
{
var exps = ((IEnumerable<object>)array.Expressions).ToArray();
for (var i = 0; i < exps.Length; i++)
{
var tartetListProperty = (exps[i] as MemberExpression).Member as PropertyInfo;
var navigationPropertyForList = tartetListProperty.GetCustomAttributes(typeof(NavigationPropertyForList)) as NavigationPropertyForList[];
if (navigationPropertyForList == null || navigationPropertyForList.Length == 0) continue;
var navigationPropertyForListString = navigationPropertyForList[0].TargetPropertyName;
if (tartetListProperty == null) continue;
var list = tartetListProperty.GetValue(obj); // WHERE I USE REFLECTION TO GET THE LIST
var listOfType = list.GetType().GetGenericArguments()[0];
var repDNI = uow.GetRepositoryDeleteNotIncludedAsyncByType(listOfType);
await repDNI(list, navigationPropertyForListString, obj.Id); // THIS IS WHERE IT FAILS
if (!res) break;
}
}
}
The repDNI object is correct and working if I do the correct casting, the only problem I am having is on getting the list, the type object { X } is surrounding my correct type.
I was able to make it work by changing the following line:
Before: var list = tartetListProperty.GetValue(obj);
After: dynamic list = tartetListProperty.GetValue(obj);
You could cast to a generic list:
await repDNI((List<T>)list, navigationPropertyForListString, obj.Id);
As far as I know this should resolve your problem while remaining the generic functionality.

Categories

Resources