C
C#4mo ago
Kye

Circular Dependency

Heyo :D, So I have started getting into somewhat bigger projects again and needed to use Dependency Injections to loose coupling and overall tightness between classes. Since 2 of my classes (and likely in the future, more class depend on each other), it's a good approach to create abstractions to expose only a fraction of the class that other classes need to access. This way, I am not exposing it concretely. However, for some reason, I am running into a circular dependency exception which I don't seem to... really understand? I get where the issue is but I can't figure out a more plausible approach. I would appreciate some feedback on what potentially can be done. To give the issue.. ILoggingManager depends on IYukkaifyEnvironment, which in turn depends on ILoggingManager..
namespace Yukkaify
{
public interface IYukkaifyEnvironment
{
bool IsHooked { get; set; }
string Root { get; set; }
}

public interface IYukkaify
{
DiscordSocketClient Client { get; set; }
CommandService Command { get; set; }
IConfiguration Configuration { get; set; }
ILoggingManager LogManager { get; set; }
IGuildVerificationManager GuildManager { get; set; }
IEventsManager EventManager { get; set; }

Task InitializeYukkaify();
Task YukkaifyReadyHandler();
}

public class Yukkaify(DiscordSocketClient client, CommandService command, IConfiguration configuration, ILoggingManager logManager, IGuildVerificationManager guildManager, IEventsManager eventManager) : IYukkaify, IYukkaifyEnvironment
{
// Here I do whatever. Since nothing is being used atm besides ILoggingManager (im still working on it)
}
}
namespace Yukkaify
{
public interface IYukkaifyEnvironment
{
bool IsHooked { get; set; }
string Root { get; set; }
}

public interface IYukkaify
{
DiscordSocketClient Client { get; set; }
CommandService Command { get; set; }
IConfiguration Configuration { get; set; }
ILoggingManager LogManager { get; set; }
IGuildVerificationManager GuildManager { get; set; }
IEventsManager EventManager { get; set; }

Task InitializeYukkaify();
Task YukkaifyReadyHandler();
}

public class Yukkaify(DiscordSocketClient client, CommandService command, IConfiguration configuration, ILoggingManager logManager, IGuildVerificationManager guildManager, IEventsManager eventManager) : IYukkaify, IYukkaifyEnvironment
{
// Here I do whatever. Since nothing is being used atm besides ILoggingManager (im still working on it)
}
}
namespace Yukkaify.Utilities
{
public interface ILoggingManager
{
IYukkaifyEnvironment Yukkaify { get; }
IQueueManager QueueManager { get; }
IConfiguration Configuration { get; }
List<string> ContentCache { get; set; }

Task ContentLogger(LoggingManager.DebugType type, string message, Exception ex = null);
Task LogReporter();
}

public class LoggingManager(IYukkaifyEnvironment yukkaify, IQueueManager queueManager, IConfiguration configuration) : ILoggingManager
{
public IYukkaifyEnvironment Yukkaify { get; } = yukkaify;

public IQueueManager QueueManager { get; } = queueManager;

public IConfiguration Configuration { get; } = configuration;

public List<string> ContentCache { get; set; } = [];
}
}
namespace Yukkaify.Utilities
{
public interface ILoggingManager
{
IYukkaifyEnvironment Yukkaify { get; }
IQueueManager QueueManager { get; }
IConfiguration Configuration { get; }
List<string> ContentCache { get; set; }

Task ContentLogger(LoggingManager.DebugType type, string message, Exception ex = null);
Task LogReporter();
}

public class LoggingManager(IYukkaifyEnvironment yukkaify, IQueueManager queueManager, IConfiguration configuration) : ILoggingManager
{
public IYukkaifyEnvironment Yukkaify { get; } = yukkaify;

public IQueueManager QueueManager { get; } = queueManager;

public IConfiguration Configuration { get; } = configuration;

public List<string> ContentCache { get; set; } = [];
}
}
37 Replies
dreadfullydistinct
To my eye, it looks like LoggingManager depends on IYukkaifyEnvironment, but Yukkaify (which the framework will attempt to create when you request IYukkaifyEnvironment) depends on LoggingManager -> circular dependency
Since 2 of my classes (and likely in the future, more class depend on each other), it's a good approach to create abstractions to expose only a fraction of the class that other classes need to access
FYI I've never heard of this practice the way you're implementing it. You should definitely have private fields / properties / methods that are implementation details and not publicly exposed, but this pattern of creating 'partial interfaces' is new to me. In your situation I would have a different concrete classes, one which implements IYukkaify and one which implements IYukkaifyEnvironment I'm not super experienced so I will stop short of saying it's wrong though
Keswiik
Keswiik4mo ago
In most cases, your classes should not be dependent on one another. This is bad design.
Kye
Kye4mo ago
that's true. circular dependency is bad design and i had thought about moving ClassA and ClassB function to ClassC and just use ClassC for things but that would sort of ? be hacky and i'd rather keep things in their designated place
Keswiik
Keswiik4mo ago
Not a question anyone can answer unless they know what you're using these interfaces for and what the intended functionality is. But there's more issues than just co-dependency. Even your ILoggingManager interface refers to the implementation LoggerManager.DebugType.
Kye
Kye4mo ago
So for instance, (and this probably sounds silly) but Root contains the path used to navigate the server and technically, i wouldn't need to reference the main class just to use Root
Keswiik
Keswiik4mo ago
I'm assuming you're placing multiple interfaces and classes in the same file and there's really no point in doing that outside of encapsulated / hidden internal types.
Kye
Kye4mo ago
i could move a new variable of Root to the class and just use that
Keswiik
Keswiik4mo ago
Out of context this means nothing to me. I have no clue what your application does or what frameworks it is using.
Kye
Kye4mo ago
currently using .NET Console Application 7.0 C# 12.0 but to explain
string Root { get; set; }
string Root { get; set; }
this is the root used to store things on the server. it acts as a path string. i am using IYukkaify in other classes just to access Root without needing to copy the same Root variable to other classes and use it privately
Keswiik
Keswiik4mo ago
Again, what does "server' mean here? Is this a web project serving an API or remote file access? Or some CLI utility?
Kye
Kye4mo ago
the server is where the application is stored and running. in this case, an Ubuntu server using dotnet to launch it and keep it up indefinitely the Root variable just tells me where the folders are on the server so i can read / write files on there
Keswiik
Keswiik4mo ago
Sounds like something that sould be set in a configuration file and passed around through some form of dependency injection.
Kye
Kye4mo ago
i am actually doing something similar
Keswiik
Keswiik4mo ago
Is this a web project? Are you using ASP.net?
Kye
Kye4mo ago
no, a .NET console aplication I had the pleasure to speak to someone in VC earlier and i was suggested to use ASP.NET which seems like a great idea but I am currently not able to transition over as quick and get the hang of a ASP.NET application especially because I would still need to learn how to utilize and manage dependencies
Keswiik
Keswiik4mo ago
Well, either way, you should probably set up proper dependency injection as it will help you reduce this bad coupling and let you make better design decisions.
Kye
Kye4mo ago
yes, i am reading up on stackoverflow right now but nothing gives me sort of a start on where I can improve my code in like, i think the best thing for me is to learn how professionals manage their dependencies and how i can adapt to it
Keswiik
Keswiik4mo ago
Not really an easy answer to that.
Kye
Kye4mo ago
managing dependencies in a large-scale application is challenging, indeed
Keswiik
Keswiik4mo ago
Well, the first thing you should do, at least in your current situation, is ask yourself why certain co-dependencies exist and then think of ways to abstract them.
Keswiik
Keswiik4mo ago
IYukkaifyEnvironment - why does this need a logger manager? And why does it need to be an interface? This could work fine as a POCO that is read from some configuration file.
Kye
Kye4mo ago
so IYukkaifyEnvironment does not need a logger, what I wanna achieve is to access the variable Root wherever needed without doing
public class Class1
{
string Root { get; set; } = "path_here";
{

public class Class2
{
string Root { get; set; } = "path_here";
{

public class Class3
{
string Root { get; set; } = "path_here";
{
public class Class1
{
string Root { get; set; } = "path_here";
{

public class Class2
{
string Root { get; set; } = "path_here";
{

public class Class3
{
string Root { get; set; } = "path_here";
{
instead, i wanna do
public class Class1
{
string Root { get; set; } = "path_here";
{

public class Class2
{
private Class1 class1 { get; set; }
public Class2(Class1 _class1)
{
class1 = _class1;
class1.Root;
}
{
public class Class1
{
string Root { get; set; } = "path_here";
{

public class Class2
{
private Class1 class1 { get; set; }
public Class2(Class1 _class1)
{
class1 = _class1;
class1.Root;
}
{
Keswiik
Keswiik4mo ago
Yes, that is what I was referring to when I said "This could work fine as a POCO that is read from some configuration file."
Kye
Kye4mo ago
however, i have many classes such as ClassX is dependend on ClassY but ClassY has an instance of ClassB which in turn has an instance of ClassY and it repeats the cycle ultimately, i wanna access class functions in other classes such as the logmanager which has useful utilities
Keswiik
Keswiik4mo ago
because you have a very poorly designed application
Kye
Kye4mo ago
but i don't wanna create new instances whenever i need it because calling that class would also instantiate logmanager pretty much access LogManager's functions without instantiating it so it doesn't create an instance of it when i call that class i could make those classes static ?
Keswiik
Keswiik4mo ago
No. First things first, learn the basics around dependency injection so you know what you can and cannot do with it. Then look back at what you have and start trying to fix your design. For example, you could do something like this:
public class YukkaifyEnvironment {
public string Root { get; init set; }
}

public class YukkaifyConfiguration {
public YukkaifyEnvironment Environment { get; init set; }

public YukkaifyConfiguration(ConfigurationManager configurationManager) {
// some code to read and set Environment
}
}

public class Yukkaify {
private readonly YukkaifyConfiguration configuration;

public Yukkaify(YukkaifyConfiguration configuration) {
this.configuration = configuration;
}
}
public class YukkaifyEnvironment {
public string Root { get; init set; }
}

public class YukkaifyConfiguration {
public YukkaifyEnvironment Environment { get; init set; }

public YukkaifyConfiguration(ConfigurationManager configurationManager) {
// some code to read and set Environment
}
}

public class Yukkaify {
private readonly YukkaifyConfiguration configuration;

public Yukkaify(YukkaifyConfiguration configuration) {
this.configuration = configuration;
}
}
Kye
Kye4mo ago
so the purpose of YukkaifyConfiguration is to.? declare YukkaifyEnvironment or
Keswiik
Keswiik4mo ago
To read the application's config from a file and create YukkaifyConfiguration and expose it to the rest of the application.
Kye
Kye4mo ago
i see.
Keswiik
Keswiik4mo ago
With dependency injection you'd also be able to remove your LoggerManager entirely. (note this would require some additional code to configure logging properly)
public class Yukkaify {
private readonly YukkaifyConfiguration configuration;

private readonly ILogger<Yukkaify> logger;

public Yukkaify(YukkaifyConfiguration configuration, ILogger<Yukkaify> logger) {
this.configuration = configuration;
this.logger = logger;
}
}
public class Yukkaify {
private readonly YukkaifyConfiguration configuration;

private readonly ILogger<Yukkaify> logger;

public Yukkaify(YukkaifyConfiguration configuration, ILogger<Yukkaify> logger) {
this.configuration = configuration;
this.logger = logger;
}
}
Kye
Kye4mo ago
isn't ILogger a part of SeriLog?
Keswiik
Keswiik4mo ago
I am referring to ILogger from Microsoft.Extensions.Logging.
Kye
Kye4mo ago
I see, but using an extension for differentl logging would only resolve the issue I have with loggingmanager's circular dependency. it wouldn't resolve future dependency issues which im trying to figure out
Keswiik
Keswiik4mo ago
Designing things with the methods demonstrated above would absolutely help.
Kye
Kye4mo ago
i will try to look into it quite frankly, I ended up using Quickwire which is the same set up in Startup but you declare [RegisterService] on top of your class and [InjectService] to use it with no problem