Inside Architecture

Notes on Enterprise Architecture, Business Alignment, Interesting Trends, and anything else that interests me this week...

How to get rid of circular references in C#

How to get rid of circular references in C#

  • Comments 4

A refers to B, B refers to A, Why can't we all just get along?

Every now and again, I see a posting on the newsgroups where someone has created a circular reference in their code structure, and they can't figure out how to get out from under it.  I'm writing this article for those folks (and so I have some place to send them when I run across this problem repeatedly).

Let's start by describing a ciruclar reference.  Let's say that I have a logging layer that is useful for recording events to a log file or a database.  Let's say that is relies on my config settings to decide where to log things.  Let's also say that I have a config settings library that allows me to write back to my config file...


//calling app:
Logging myLogObject = new Logging();
myLogObject.WriteToLog("We are here!");
MyConfig cnf = new MyConfig();
cnf.SetSetting("/MyName","mud", myLogObject);

The class may look like this:

public class Logging {
 public Logging()
 {
   MyConfig cnf = new MyConfig();

   LoggingLocation = cnf.GetSetting("//Log/Location");
   if (LoggingLocation == "File")
   {
      // save logs to a file
   } 
   else
   {
        // save logs to a database
   }
 }

 public void WriteToLog(String LogMessage, int Severity)
 {
   // write the log message
 }

}

If you notice, my little logging app refers to my config file library in the constructor of the logging object.  So now the logging object refers to the config object in code.

Let's say, however, that we want to write a log entry each time a value is changed in the config file. 

public class MyConfig
{
   public MyConfig() { }
   public string GetSetting(string SettingXPath)
   {
      // go get the setting
   }
   public void SetSetting(string SettingXPath, string newValue, Logging myLog)
   {
      // set the string and...
      myLog.WriteToLog("Updated " + SettingXPath + " : " + newValue);
   }
}

OK, so I removed most of the interesting code.  I left in the reference, though.  Now the config object refers to the logging object.  Note that I am passing in actual objects, and not using static methods.  You can get here just as easily if you use static methods.  However, digging yourself out requires real objects, as you will see.

Now, compile them both.  One class will require the other.  If they are in the same assembly, it won't matter.  However, if they are in seperate DLLs, as I want to use them, we have a problem, because neither wants to be the one to compile first.

The solution is to decide who wins: the config object or the logging object.  The winner will be the first to compile.  It will contain a definition of an interface that BOTH will use.  (Note: you can put the interface in a third DLL that both will refer to... a little more complicated to describe, but the same effect.  I'll let you decide what you like better :-).

For this example, I will pick the config object as the winner. In this case, the logging object will continue to refer to the config object, but we will break the bond that requires the config object to refer to the logging object.

Let's add the Interface to the Config object assembly:

public interface IMyLogging
{
   void WriteToLog(String LogMessage, int Severity);
}

Let's change the code in the call to SetSetting:

   public void SetSetting(string SettingXPath, string newValue, IMyLogging myLog)
   {
      // set the string and...
      myLog.WriteToLog("Updated " + SettingXPath + " : " + newValue);
   }

You will notice that the only think I changed was the declaration.  The rest of the code is unchanged.

Now, in the Logging object:

public class Logging : IMyLogging {
// the rest is unchanged
}

Now, the Logging assembly continues to rely on the config assembly, but instead of just relying on it for the definition of our config class, we also rely on it for the definition of the IMyLogging interface.

On the other hand, the config class is self sufficient.  It doesn't need any other class to define anything.

Now, both assemblies will compile just fine.

  • I believe you mean (in the first code section), instead of
    <i>//calling app:
    Logging myLogObject = new Logging();
    myLogObject.WriteToLog("We are here!");
    MyConfig cnf = new MyConfig();
    myLogObject.SetSetting("/MyName","mud", myLogObject);</i>

    this:
    <i>//calling app:
    Logging myLogObject = new Logging();
    myLogObject.WriteToLog("We are here!");
    MyConfig cnf = new MyConfig();
    cnf.SetSetting("/MyName","mud", myLogObject);
    </i>
  • Good catch. I fixed the error.
  • Good example why we need interface. :-)
  • I never really understood the reason for requiring all references to be satisfied at compile-time. I mean, Java doesn't have a problem because it compiles each class into a separate .class file anyway. <br> <br>But even on Linux, and even for native libraries (i.e. *.so files), you can have undefined references at compile time, as long as the image loader can satisfy them all at run-time. Seems like a much nicer solution to me...
Page 1 of 1 (4 items)