I wish to calculate the Access To Foreign Data metric for my methods. Furthermore, I would like to know from which objects my methods are accessing fields and properties to determine the existence of feature envy code smells.
For this example:
public class DoctorService
{
private List<Doctor> _doctors;
public Doctor FindAvailableDoctor(DateRange timeSpan)
{
foreach (Doctor d in _doctors)
{
foreach(DateRange holiday in d.HolidayDates)
{
d.Test = null;
if (!holiday.OverlapsWith(timeSpan)) return d;
}
}
return null;
}
}
and specifically the FindAvailableDoctor method, I would like to build a list of accessed fields that would contain the HolidayDates and Test property of the Doctor class. I would also like to know both these properties belong to the Doctor class (identified by the class name and its namespace).
I've made the following code to acomplish this:
var accessedFields = member.DescendantNodes().OfType<MemberAccessExpressionSyntax>();
foreach (var field in accessedFields)
{
var symbol = semanticModel.GetSymbolInfo(field.Expression).Symbol;
switch(symbol)
{
case ILocalSymbol local:
fields.Add(new CaDETMember { Name = local.Type + "|" + local.Name });
break;
case IPropertySymbol prop:
fields.Add(new CaDETMember { Name = prop.ContainingSymbol + "|" + field.ToString() });
break;
case IParameterSymbol param:
fields.Add(new CaDETMember { Name = param.Type + "|" + field.ToString() });
break;
};
}
and have started fiddling about with the fields of the Symbol API. However, this solution is both unclean and I am pretty sure will miss some edge cases for non-trivial code.
What would be a better way to extract the field and property names accessed by a method, so that I can also know which class the accessed fields and properties belong to?
Edit: Based on Jason's answer, I went with the following solution:
var accessedFields = semanticModel.GetOperation(member).Descendants().OfType<IMemberReferenceOperation>();
foreach (var field in accessedFields)
{
fields.Add(new CaDETMember {Name = field.Member.ToDisplayString()});
}
return fields;
Your approach is actually a perfectly fine approach, so don't feel too bad about it. There's a second way to do it which is to use the IOperation APIs. If you call SemanticModel.GetOperation() that gives you a tree of IOperations you can walk through which represent the semantic operations being performed at various points in the code. In particular there's an IMemberReferenceOperation that will point to the member being referenced. So something like:
var memberReferences = semanticModel.GetOperation(methodSyntax).Descendants().OfType<IMemberReferenceOperation>()
will let you look at those and then get the symbols from there. There's other operations for local accesses too.