How to define away code based on .NET framework ve

2019-05-15 23:18发布

问题:

I have a function that would love to take advantage of the "correct" way to do something that was provided in .NET 4.5:

public DbDataAdapater CreateDataAdapter(DbConnection connection)
{
   #IFDEF (NET45)
      return DbProviderFactories.GetFactory(connection).CreateDataAdapter();
   #ELSE
      //We can't construct an adapter directly
      //So let's run around the block 3 times, before potentially crashing
      DbDataAdapter adapter; 

      if (connection is System.Data.SqlClient.SqlConnection)
         return new System.Data.SqlClient.SqlDataAdapter();
      if (connection is System.Data.OleDb.OleDbConnection)
         return new System.Data.OleDb.OleDbDataAdapter();
      if (connection is System.Data.Odbc.OdbcConnection)
         return new System.Data.Odbc.OdbcDataAdapter();
      //Add more DbConnection kinds as they become invented
      if (connection is SqlCeConnection)
         return new SqlCeDataAdapter();
      if (connection is MySqlConnection)
         return new MySqlDataAdapter();
      if (connection is DB2Connection)
         return new DB2DataAdapter();

      throw new Exception("[CreateDataAdapter] Unknown DbConnection type: " + connection.GetType().FullName);
   #END
}

The only way i've been able to find to make this work is for everyone who uses this shared code to alter their Visual Studio solution.

Which not going to happen; it has to just work, or it's not going to be used at all.

Is there a ways to define away non-functional code when the solution targets earlier versions of the .NET framework?

In other words, it would be great if this compiled:

public DbDataAdapter CreateDataAdapter(DbConnection conn)
{
   if (System.Runtime.Version >= 45)
      return DbProviderFactories.GetFactor(connection).CreateDataAdapter();
   else
   {
      //...snip the hack...
   }
}

But it doesn't compile if the targeted framework is too low.

回答1:

If the priority is to have this working with minimal compile time setup then I would just move the check to runtime and use reflection to check if the method is available and use the workaround when its not. This as the added benefit that an application targeting .NET 4.0 running in a client with 4.5 installed will use the better approach.

Sample:

static Func<DbConnection, DbProviderFactory> GetFactoryDelegate;

private static void Main() {
    Console.WriteLine(GetFactory(new SqlConnection()).CreateDataAdapter());
}
private static DbProviderFactory GetFactory(DbConnection connection) {
    if (GetFactoryDelegate == null) {
        var frameworkGetFactoryMethod = typeof (DbProviderFactories).GetMethod(
            "GetFactory", BindingFlags.Static | BindingFlags.Public,
            null, new[] { typeof (DbConnection) }, null);

        if (frameworkGetFactoryMethod != null) {
            GetFactoryDelegate = (Func<DbConnection, DbProviderFactory>)
                Delegate.CreateDelegate(
                    typeof(Func<DbConnection, DbProviderFactory>),
                    frameworkGetFactoryMethod);
        }
        else { GetFactoryDelegate = GetFactoryThroughWorkaround; }
    }
    return GetFactoryDelegate(connection);
}
private static DbProviderFactory GetFactoryThroughWorkaround(
    DbConnection connection) {

    if (connection is SqlConnection)
        return SqlClientFactory.Instance;
    // ... Remaining cases
    throw new NotSupportedException();
}

This approach is much similar to what is current best practice in the JavaScript world of checking that a feature is available instead of performing browser sniffing. The .NET counterpart does not have the same elegance due to requiring the use of reflection. However, the code could be made prettier if the requirement for dynamic is acceptable.



回答2:

This answer is the same as the marked answer, but easier to digest for others looking for a solution which is not based on the user's original post scenario.


Use reflection to determine if the class exists. If it does, then dynamically create and use it, otherwise use a workaround class or code which can be defined for that scenario.

Here is code I used for an AggregateException which is .Net 4 and greater only:

var aggregatException = Type.GetType("System.AggregateException");

if (aggregatException != null) // .Net 4 or greater
{
    throw ((Exception)Activator.CreateInstance(aggregatException, ps.Streams.Error.Select(err => err.Exception)));
}

// Else all other non .Net 4 or less versions
throw ps.Streams.Error.FirstOrDefault()?.Exception 
      ?? new Exception("Powershell Exception Encountered."); // Sanity check operation, should not hit.


回答3:

As long as code is not JIT'ed it can be referencing non-existing methods/classes/assemblies. As the smallest unit of JIT is whole function you need to put code that refers to potentially missing methods/classes from a function and dynamically decide when to call the function:

public DbDataAdapter CreateDataAdapter(DbConnection conn)
{
   if (System.Runtime.Version >= 45)
   {
      return Hide45DependencyFromJit(connection);
   }
   else
   {
      //...snip the hack...
   }
}

private void Hide45DependencyFromJit(... connection)
{
      return DbProviderFactories.GetFactor(connection).CreateDataAdapter();
}

Notes:

  • I'm not sure if there are issues with 4/4.5 frameworks, this approach worked for me for other "missing functionality" case.
  • it may not work if Ngen'ed (not sure).
  • you have to set target framework higher and be very careful and avoid mistakenly taking dependencies on new methods.


标签: c# .net .net-4.5