expose a referenced type (class) without need for additional reference

You need to separate all the common classes you use across all your layers into a new dll, then reference this dll on every project.

Try to use interfaces so you can work over the contract (the functionality) instead of the concrete implementation. It will help you to avoid unnecessary references.

// common dll
public interface IMyClass
{
    string MyData { get; set; }
    IMyClass GetMyClass();
}

// dll1
public class myClass : IMyClass
{
    public string MyData { get; set; }
    public IMyClass GetMyClass() { return new myClass() { MyData = "abc" }; }
}

// dll2
public class myClass2
{
    public IMyClass GetMyClass()
    {
         var c1 = new myClass();
         var c2 = c1.GetMyClass();
         return c2;
    }
}

// exe (references common and dll2)
public class Program
{
    public static void Main(string[] args)
    {
        var c1 = new myClass2();
        IMyClass c2 = c1.GetMyClass();
        Console.Writeline(c2.MyData);
    }
}

Seems no way to achieve this, if myClass is defined in dll1, since objects of type myClass may be instantiated at runtime. To avoid this, you need to change the return type of GetMyClass() in dll2 to return something defined in dll2. It can be a class quite similar to myClass and having the same properties (you can even use tools like AutoMapper to easily convert between objects), but it definitely should be in dll2. Something like:

// dll1
class myClass
{
    ...
}

myClass GetMyClass()
{
    ...
}

// dll2
class myClass2
{
    public myClass2(myClass c)
    {
        // instantiate myClass2 with values from myClass
    }
}

myClass2 GetMyClass()
{
    // somehow get myClass and convert it to myClass2 here
}

We do something similar to this in our local code. You can load the assembly at runtime, scan the types it contains using reflection, and again using reflection call functions and instantiate types from that dll, without ever referencing it directly in the project.

some of the key functions you will need are:

Assembly.LoadFrom(path); //get the assembly as a local object
Activator.CreateInstance(type); //create an instance of a type
Assembly.GetType(string);//fetch a type by name from the assembly

Once you have the type, basic reflection will give you pretty much every other piece you need.

Here is the snippet from my local code:

asm = Assembly.LoadFrom(Path.Combine(Environment.CurrentDirectory, filePath));

Type[] types = asm.GetTypes();
for (var x = 0; x < types.Length; x++)
{
    var interfaces = types[x].GetInterfaces();
    for (var y = 0; y < interfaces.Length; y++)
    {
        if (interfaces[y].Name.Equals("MyTypeName"))
        {
            isValidmod = true;
            var p = (IMyType)Activator.CreateInstance(types[x]);
                            //Other stuff
            }
    }

Also note: this is pretty old code now. It has been neither reviewed nor refactored in years, so its essentially modern as of .NET 1.1, BUT: It illustrates the point. How to load a type from a remote assembly that is NOT locally referenced.

Additionally, this is part of an engine that loads some 50 of these, given a rigid folder structure, which is why its so generic looking. Take what you need from it.